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 const QStringList parts = version.split(
' ', Qt::SkipEmptyParts );
374 if ( !parts.empty() )
376 const QStringList verparts = parts.at( 0 ).split(
'.', Qt::SkipEmptyParts );
377 above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
381 sqlite3_free_table( results );
384 char *errMsg =
nullptr;
385 ret = sqlite3_exec( sqlite_handle, above41 ?
"SELECT InitSpatialMetadata(1)" :
"SELECT InitSpatialMetadata()", nullptr, nullptr, &errMsg );
387 if ( ret != SQLITE_OK )
389 QString errCause = tr(
"Unable to initialize SpatialMetadata:\n" );
390 errCause += QString::fromUtf8( errMsg );
391 showWarning( errCause );
392 sqlite3_free( errMsg );
395 spatial_ref_sys_init( sqlite_handle, 0 );
397 ( void )sqlite_handle;
401bool QgsOfflineEditing::createOfflineDb(
const QString &offlineDbPath, ContainerType containerType )
404 char *errMsg =
nullptr;
405 const QFile newDb( offlineDbPath );
406 if ( newDb.exists() )
408 QFile::remove( offlineDbPath );
413 const QFileInfo fullPath = QFileInfo( offlineDbPath );
414 const QDir path = fullPath.dir();
417 QDir().mkpath( path.absolutePath() );
420 const QString dbPath = newDb.fileName();
423 switch ( containerType )
427 OGRSFDriverH hGpkgDriver = OGRGetDriverByName(
"GPKG" );
430 showWarning( tr(
"Creation of database failed. GeoPackage driver not found." ) );
437 showWarning( tr(
"Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
449 ret = database.
open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
nullptr );
453 QString errCause = tr(
"Could not create a new database\n" );
455 showWarning( errCause );
459 ret = sqlite3_exec( database.get(),
"PRAGMA foreign_keys = 1",
nullptr,
nullptr, &errMsg );
460 if ( ret != SQLITE_OK )
462 showWarning( tr(
"Unable to activate FOREIGN_KEY constraints" ) );
463 sqlite3_free( errMsg );
466 initializeSpatialMetadata( database.get() );
470void QgsOfflineEditing::createLoggingTables(
sqlite3 *db )
473 QString sql = QStringLiteral(
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
476 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
479 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
483 sql = QStringLiteral(
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
487 sql = QStringLiteral(
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER, 'remote_pk' TEXT)" );
491 sql = QStringLiteral(
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
492 sql += QLatin1String(
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
496 sql = QStringLiteral(
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
500 sql = QStringLiteral(
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
504 sql = QStringLiteral(
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
508 sql = QStringLiteral(
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
516void QgsOfflineEditing::convertToOfflineLayer(
QgsVectorLayer *layer,
sqlite3 *db,
const QString &offlineDbPath,
bool onlySelected, ContainerType containerType,
const QString &layerNameSuffix )
518 if ( !layer || !layer->
isValid() )
520 QgsDebugMsgLevel( QStringLiteral(
"Layer %1 is invalid and cannot be copied" ).arg( layer ? layer->
id() : QStringLiteral(
"<UNKNOWN>" ) ), 4 );
524 const QString tableName = layer->
id();
525 QgsDebugMsgLevel( QStringLiteral(
"Creating offline table %1 ..." ).arg( tableName ), 4 );
528 std::unique_ptr<QgsVectorLayer> newLayer;
530 switch ( containerType )
534#ifdef HAVE_SPATIALITE
536 QString sql = QStringLiteral(
"CREATE TABLE '%1' (" ).arg( tableName );
539 for (
const auto &field : providerFields )
542 const QMetaType::Type type = field.type();
543 if ( type == QMetaType::Type::Int || type == QMetaType::Type::LongLong )
545 dataType = QStringLiteral(
"INTEGER" );
547 else if ( type == QMetaType::Type::Double )
549 dataType = QStringLiteral(
"REAL" );
551 else if ( type == QMetaType::Type::QString )
553 dataType = QStringLiteral(
"TEXT" );
555 else if ( type == QMetaType::Type::QStringList || type == QMetaType::Type::QVariantList )
557 dataType = QStringLiteral(
"TEXT" );
558 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() ) );
562 showWarning( tr(
"%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
565 sql += delim + QStringLiteral(
"'%1' %2" ).arg( field.name(), dataType );
570 int rc = sqlExec( db, sql );
581 geomType = QStringLiteral(
"POINT" );
584 geomType = QStringLiteral(
"MULTIPOINT" );
587 geomType = QStringLiteral(
"LINESTRING" );
590 geomType = QStringLiteral(
"MULTILINESTRING" );
593 geomType = QStringLiteral(
"POLYGON" );
596 geomType = QStringLiteral(
"MULTIPOLYGON" );
603 QString zmInfo = QStringLiteral(
"XY" );
612 if ( layer->
crs().
authid().startsWith( QLatin1String(
"EPSG:" ), Qt::CaseInsensitive ) )
614 epsgCode = layer->
crs().
authid().mid( 5 );
619 showWarning( tr(
"Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->
name(), layer->
crs().
authid() ) );
622 const QString sqlAddGeom = QStringLiteral(
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')" )
623 .arg( tableName, epsgCode, geomType, zmInfo );
626 const QString sqlCreateIndex = QStringLiteral(
"SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
628 if ( rc == SQLITE_OK )
630 rc = sqlExec( db, sqlAddGeom );
631 if ( rc == SQLITE_OK )
633 rc = sqlExec( db, sqlCreateIndex );
638 if ( rc != SQLITE_OK )
640 showWarning( tr(
"Filling SpatiaLite for layer %1 failed" ).arg( layer->
name() ) );
645 const QString connectionString = QStringLiteral(
"dbname='%1' table='%2'%3 sql=" )
647 tableName, layer->
isSpatial() ?
"(Geometry)" :
"" );
649 newLayer = std::make_unique<QgsVectorLayer>( connectionString,
650 layer->
name() + layerNameSuffix, QStringLiteral(
"spatialite" ), options );
654 showWarning( tr(
"No Spatialite support available" ) );
662 char **options =
nullptr;
664 options = CSLSetNameValue( options,
"OVERWRITE",
"YES" );
665 options = CSLSetNameValue( options,
"IDENTIFIER", tr(
"%1 (offline)" ).arg( layer->
id() ).toUtf8().constData() );
666 options = CSLSetNameValue( options,
"DESCRIPTION", layer->
dataComment().toUtf8().constData() );
669 const QString fidBase( QStringLiteral(
"fid" ) );
670 QString fid = fidBase;
674 fid = fidBase +
'_' + QString::number( counter );
677 if ( counter == 10000 )
679 showWarning( tr(
"Cannot make FID-name for GPKG " ) );
683 options = CSLSetNameValue( options,
"FID", fid.toUtf8().constData() );
687 options = CSLSetNameValue( options,
"GEOMETRY_COLUMN",
"geom" );
688 options = CSLSetNameValue( options,
"SPATIAL_INDEX",
"YES" );
691 OGRSFDriverH hDriver =
nullptr;
694 OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS,
static_cast<OGRwkbGeometryType
>( layer->
wkbType() ), options );
695 CSLDestroy( options );
700 showWarning( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
705 for (
const auto &field : providerFields )
707 const QString fieldName( field.name() );
708 const QMetaType::Type type = field.type();
709 OGRFieldType ogrType( OFTString );
710 OGRFieldSubType ogrSubType = OFSTNone;
711 if ( type == QMetaType::Type::Int )
712 ogrType = OFTInteger;
713 else if ( type == QMetaType::Type::LongLong )
714 ogrType = OFTInteger64;
715 else if ( type == QMetaType::Type::Double )
717 else if ( type == QMetaType::Type::QTime )
719 else if ( type == QMetaType::Type::QDate )
721 else if ( type == QMetaType::Type::QDateTime )
722 ogrType = OFTDateTime;
723 else if ( type == QMetaType::Type::Bool )
725 ogrType = OFTInteger;
726 ogrSubType = OFSTBoolean;
728 else if ( type == QMetaType::Type::QStringList || type == QMetaType::Type::QVariantList )
731 ogrSubType = OFSTJSON;
732 showWarning( tr(
"Field '%1' from layer %2 has been converted from a list to a JSON-formatted string value." ).arg( fieldName, layer->
name() ) );
737 const int ogrWidth = field.length();
740 OGR_Fld_SetWidth( fld.get(), ogrWidth );
741 if ( ogrSubType != OFSTNone )
742 OGR_Fld_SetSubType( fld.get(), ogrSubType );
744 if ( OGR_L_CreateField( hLayer, fld.get(),
true ) != OGRERR_NONE )
746 showWarning( tr(
"Creation of field %1 failed (OGR error: %2)" )
747 .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
755 OGR_L_ResetReading( hLayer );
756 if ( CPLGetLastErrorType() != CE_None )
758 const QString msg( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
764 const QString uri = QStringLiteral(
"%1|layername=%2|option:QGIS_FORCE_WAL=ON" ).arg( offlineDbPath, tableName );
766 newLayer = std::make_unique<QgsVectorLayer>( uri, layer->
name() + layerNameSuffix, QStringLiteral(
"ogr" ), layerOptions );
771 if ( newLayer && newLayer->isValid() )
775 newLayer->startEditing();
783 if ( !selectedFids.isEmpty() )
797 long long featureCount = 1;
798 const int remotePkIdx = getLayerPkIdx( layer );
800 QList<QgsFeatureId> remoteFeatureIds;
801 QStringList remoteFeaturePks;
804 remoteFeatureIds << f.
id();
805 remoteFeaturePks << ( remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString() );
812 QgsAttributes newAttrs( containerType ==
GPKG ? attrs.count() + 1 : attrs.count() );
813 for (
int it = 0; it < attrs.count(); ++it )
815 const QVariant attr = attrs.at( it );
816 newAttrs[column++] = attr;
820 newLayer->addFeature( f );
824 if ( newLayer->commitChanges() )
830 const int layerId = getOrCreateLayerId( db, layer->
id() );
831 QList<QgsFeatureId> offlineFeatureIds;
836 offlineFeatureIds << f.
id();
840 sqlExec( db, QStringLiteral(
"BEGIN" ) );
841 const int remoteCount = remoteFeatureIds.size();
842 for (
int i = 0; i < remoteCount; i++ )
845 if ( i < offlineFeatureIds.count() )
847 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ), remoteFeaturePks.at( i ) );
851 showWarning( tr(
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
856 sqlExec( db, QStringLiteral(
"COMMIT" ) );
860 showWarning( newLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
874 QStringList notNullFieldNames;
875 for (
const QgsField &field : fields )
879 notNullFieldNames << field.name();
883 layer->
setDataSource( newLayer->source(), newLayer->name(), newLayer->dataProvider()->name() );
885 for (
const QgsField &field : fields )
895 if ( notNullFieldNames.contains( field.name() ) )
897 notNullFieldNames.removeAll( field.name() );
908void QgsOfflineEditing::applyAttributesAdded(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
910 Q_ASSERT( remoteLayer );
912 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 );
913 QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
916 const QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
919 QMap < QMetaType::Type, QString > typeNameLookup;
920 for (
int i = 0; i < nativeTypes.size(); i++ )
928 for (
int i = 0; i < fields.size(); i++ )
932 if ( typeNameLookup.contains( field.
type() ) )
940 showWarning( QStringLiteral(
"Could not add attribute '%1' of type %2" ).arg( field.
name() ).arg( field.
type() ) );
949 Q_ASSERT( offlineLayer );
950 Q_ASSERT( remoteLayer );
952 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
953 const QList<int> featureIdInts = sqlQueryInts( db, sql );
955 for (
const int id : featureIdInts )
975 const int newAttrsCount = remoteLayer->
fields().
count();
976 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
980 const QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
983 for (
int it = 0; it < attrs.count(); ++it )
985 const int remoteAttributeIndex = attrLookup.value( it, -1 );
987 if ( remoteAttributeIndex == -1 )
989 QVariant attr = attrs.at( it );
990 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QStringList )
992 if ( attr.userType() == QMetaType::Type::QStringList || attr.userType() == QMetaType::Type::QVariantList )
994 attr = attr.toStringList();
1001 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QVariantList )
1003 if ( attr.userType() == QMetaType::Type::QStringList || attr.userType() == QMetaType::Type::QVariantList )
1005 attr = attr.toList();
1012 newAttrs[ remoteAttributeIndex ] = attr;
1023void QgsOfflineEditing::applyFeaturesRemoved(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId )
1025 Q_ASSERT( remoteLayer );
1027 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1028 const QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
1033 for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
1035 const QgsFeatureId fid = remoteFid( db, layerId, *it, remoteLayer );
1044 Q_ASSERT( offlineLayer );
1045 Q_ASSERT( remoteLayer );
1047 const QString sql = QStringLiteral(
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
1048 const AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
1052 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1054 for (
int i = 0; i < values.size(); i++ )
1056 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1057 QgsDebugMsgLevel( QStringLiteral(
"Offline changeAttributeValue %1 = %2" ).arg( attrLookup[ values.at( i ).attr ] ).arg( values.at( i ).value ), 4 );
1059 const int remoteAttributeIndex = attrLookup[ values.at( i ).attr ];
1060 QVariant attr = values.at( i ).value;
1061 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QStringList )
1065 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QVariantList )
1076void QgsOfflineEditing::applyGeometryChanges(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
1078 Q_ASSERT( remoteLayer );
1080 const QString sql = QStringLiteral(
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
1081 const GeometryChanges values = sqlQueryGeometryChanges( db, sql );
1085 for (
int i = 0; i < values.size(); i++ )
1087 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1097 Q_ASSERT( remoteLayer );
1103 QMap < QgsFeatureId, QString > newRemoteFids;
1110 const int remotePkIdx = getLayerPkIdx( remoteLayer );
1115 if ( offlineFid( db, layerId, f.
id() ) == -1 )
1117 newRemoteFids[ f.
id()] = remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString();
1125 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1126 const QList<int> newOfflineFids = sqlQueryInts( db, sql );
1128 if ( newRemoteFids.size() != newOfflineFids.size() )
1136 sqlExec( db, QStringLiteral(
"BEGIN" ) );
1137 for ( QMap<QgsFeatureId, QString>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
1139 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key(), it.value() );
1141 sqlExec( db, QStringLiteral(
"COMMIT" ) );
1148 Q_ASSERT( offlineLayer );
1149 Q_ASSERT( remoteLayer );
1153 QMap <
int ,
int > attrLookup;
1156 for (
int i = 0; i < offlineAttrs.size(); i++ )
1165void QgsOfflineEditing::showWarning(
const QString &message )
1167 emit
warning( tr(
"Offline Editing Plugin" ), message );
1174 if ( !dbPath.isEmpty() )
1177 const int rc = database.
open( absoluteDbPath );
1178 if ( rc != SQLITE_OK )
1180 QgsDebugError( QStringLiteral(
"Could not open the SpatiaLite logging database" ) );
1181 showWarning( tr(
"Could not open the SpatiaLite logging database" ) );
1191int QgsOfflineEditing::getOrCreateLayerId(
sqlite3 *db,
const QString &qgisLayerId )
1193 QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
1194 int layerId = sqlQueryInt( db, sql, -1 );
1195 if ( layerId == -1 )
1198 sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
1199 const int newLayerId = sqlQueryInt( db, sql, -1 );
1202 sql = QStringLiteral(
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
1207 sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
1210 layerId = newLayerId;
1216int QgsOfflineEditing::getCommitNo(
sqlite3 *db )
1218 const QString sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
1219 return sqlQueryInt( db, sql, -1 );
1222void QgsOfflineEditing::increaseCommitNo(
sqlite3 *db )
1224 const QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
1230 const QString sql = QStringLiteral(
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3, %4 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid ).arg( sqlEscape( remotePk ) );
1236 const int pkIdx = getLayerPkIdx( remoteLayer );
1240 const QString sql = QStringLiteral(
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1241 return sqlQueryInt( db, sql, -1 );
1244 const QString sql = QStringLiteral(
"SELECT \"remote_pk\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1245 QString defaultValue;
1246 const QString pkValue = sqlQueryStr( db, sql, defaultValue );
1248 if ( pkValue.isNull() )
1253 const QString pkFieldName = remoteLayer->
fields().
at( pkIdx ).
name();
1264 const QString sql = QStringLiteral(
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
1265 return sqlQueryInt( db, sql, -1 );
1270 const QString sql = QStringLiteral(
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
1271 return ( sqlQueryInt( db, sql, 0 ) > 0 );
1274int QgsOfflineEditing::sqlExec(
sqlite3 *db,
const QString &sql )
1276 char *errmsg =
nullptr;
1277 const int rc = sqlite3_exec( db, sql.toUtf8(),
nullptr,
nullptr, &errmsg );
1278 if ( rc != SQLITE_OK )
1280 showWarning( errmsg );
1285QString QgsOfflineEditing::sqlQueryStr(
sqlite3 *db,
const QString &sql, QString &defaultValue )
1287 sqlite3_stmt *stmt =
nullptr;
1288 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1290 showWarning( sqlite3_errmsg( db ) );
1291 return defaultValue;
1294 QString value = defaultValue;
1295 const int ret = sqlite3_step( stmt );
1296 if ( ret == SQLITE_ROW )
1298 value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) );
1300 sqlite3_finalize( stmt );
1305int QgsOfflineEditing::sqlQueryInt(
sqlite3 *db,
const QString &sql,
int defaultValue )
1307 sqlite3_stmt *stmt =
nullptr;
1308 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1310 showWarning( sqlite3_errmsg( db ) );
1311 return defaultValue;
1314 int value = defaultValue;
1315 const int ret = sqlite3_step( stmt );
1316 if ( ret == SQLITE_ROW )
1318 value = sqlite3_column_int( stmt, 0 );
1320 sqlite3_finalize( stmt );
1325QList<int> QgsOfflineEditing::sqlQueryInts(
sqlite3 *db,
const QString &sql )
1329 sqlite3_stmt *stmt =
nullptr;
1330 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1332 showWarning( sqlite3_errmsg( db ) );
1336 int ret = sqlite3_step( stmt );
1337 while ( ret == SQLITE_ROW )
1339 values << sqlite3_column_int( stmt, 0 );
1341 ret = sqlite3_step( stmt );
1343 sqlite3_finalize( stmt );
1348QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded(
sqlite3 *db,
const QString &sql )
1350 QList<QgsField> values;
1352 sqlite3_stmt *stmt =
nullptr;
1353 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1355 showWarning( sqlite3_errmsg( db ) );
1359 int ret = sqlite3_step( stmt );
1360 while ( ret == SQLITE_ROW )
1362 const QgsField field( QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) ),
1363 static_cast< QMetaType::Type
>( sqlite3_column_int( stmt, 1 ) ),
1365 sqlite3_column_int( stmt, 2 ),
1366 sqlite3_column_int( stmt, 3 ),
1367 QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 4 ) ) ) );
1370 ret = sqlite3_step( stmt );
1372 sqlite3_finalize( stmt );
1381 sqlite3_stmt *stmt =
nullptr;
1382 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1384 showWarning( sqlite3_errmsg( db ) );
1388 int ret = sqlite3_step( stmt );
1389 while ( ret == SQLITE_ROW )
1391 values << sqlite3_column_int( stmt, 0 );
1393 ret = sqlite3_step( stmt );
1395 sqlite3_finalize( stmt );
1400QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges(
sqlite3 *db,
const QString &sql )
1402 AttributeValueChanges values;
1404 sqlite3_stmt *stmt =
nullptr;
1405 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1407 showWarning( sqlite3_errmsg( db ) );
1411 int ret = sqlite3_step( stmt );
1412 while ( ret == SQLITE_ROW )
1414 AttributeValueChange change;
1415 change.fid = sqlite3_column_int( stmt, 0 );
1416 change.attr = sqlite3_column_int( stmt, 1 );
1417 change.value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 2 ) ) );
1420 ret = sqlite3_step( stmt );
1422 sqlite3_finalize( stmt );
1427QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges(
sqlite3 *db,
const QString &sql )
1429 GeometryChanges values;
1431 sqlite3_stmt *stmt =
nullptr;
1432 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1434 showWarning( sqlite3_errmsg( db ) );
1438 int ret = sqlite3_step( stmt );
1439 while ( ret == SQLITE_ROW )
1441 GeometryChange change;
1442 change.fid = sqlite3_column_int( stmt, 0 );
1443 change.geom_wkt = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 1 ) ) );
1446 ret = sqlite3_step( stmt );
1448 sqlite3_finalize( stmt );
1453void QgsOfflineEditing::committedAttributesAdded(
const QString &qgisLayerId,
const QList<QgsField> &addedAttributes )
1460 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1461 const int commitNo = getCommitNo( database.get() );
1463 for (
const QgsField &field : addedAttributes )
1465 const QString sql = QStringLiteral(
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1468 .arg( field.
name() )
1469 .arg( field.
type() )
1473 sqlExec( database.get(), sql );
1476 increaseCommitNo( database.get() );
1479void QgsOfflineEditing::committedFeaturesAdded(
const QString &qgisLayerId,
const QgsFeatureList &addedFeatures )
1486 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1490 const QString dataSourceString = layer->
source();
1496 if ( !offlinePath.contains(
".gpkg" ) )
1498 tableName = uri.
table();
1503 const QVariantMap decodedUri = ogrProviderMetaData->
decodeUri( dataSourceString );
1504 tableName = decodedUri.value( QStringLiteral(
"layerName" ) ).toString();
1505 if ( tableName.isEmpty() )
1507 showWarning( tr(
"Could not deduce table name from data source %1." ).arg( dataSourceString ) );
1512 const QString sql = QStringLiteral(
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( tableName ).arg( addedFeatures.size() );
1513 const QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1514 for (
int i = newFeatureIds.size() - 1; i >= 0; i-- )
1516 const QString sql = QStringLiteral(
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1518 .arg( newFeatureIds.at( i ) );
1519 sqlExec( database.get(), sql );
1523void QgsOfflineEditing::committedFeaturesRemoved(
const QString &qgisLayerId,
const QgsFeatureIds &deletedFeatureIds )
1530 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1534 if ( isAddedFeature( database.get(), layerId,
id ) )
1537 const QString sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg(
id );
1538 sqlExec( database.get(), sql );
1542 const QString sql = QStringLiteral(
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1545 sqlExec( database.get(), sql );
1550void QgsOfflineEditing::committedAttributeValuesChanges(
const QString &qgisLayerId,
const QgsChangedAttributesMap &changedAttrsMap )
1557 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1558 const int commitNo = getCommitNo( database.get() );
1560 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1563 if ( isAddedFeature( database.get(), layerId, fid ) )
1569 for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1571 QString value = it.value().userType() == QMetaType::Type::QStringList || it.value().userType() == QMetaType::Type::QVariantList ?
QgsJsonUtils::encodeValue( it.value() ) : it.value().toString();
1572 value.replace( QLatin1String(
"'" ), QLatin1String(
"''" ) );
1573 const QString sql = QStringLiteral(
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1579 sqlExec( database.get(), sql );
1583 increaseCommitNo( database.get() );
1586void QgsOfflineEditing::committedGeometriesChanges(
const QString &qgisLayerId,
const QgsGeometryMap &changedGeometries )
1593 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1594 const int commitNo = getCommitNo( database.get() );
1596 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1599 if ( isAddedFeature( database.get(), layerId, fid ) )
1605 const QString sql = QStringLiteral(
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1609 .arg( geom.
asWkt() );
1610 sqlExec( database.get(), sql );
1615 increaseCommitNo( database.get() );
1618void QgsOfflineEditing::startListenFeatureChanges()
1620 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1629 this, &QgsOfflineEditing::committedAttributesAdded );
1631 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1633 this, &QgsOfflineEditing::committedGeometriesChanges );
1636 this, &QgsOfflineEditing::committedFeaturesAdded );
1638 this, &QgsOfflineEditing::committedFeaturesRemoved );
1641void QgsOfflineEditing::stopListenFeatureChanges()
1643 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1652 this, &QgsOfflineEditing::committedAttributesAdded );
1654 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1656 this, &QgsOfflineEditing::committedGeometriesChanges );
1659 this, &QgsOfflineEditing::committedFeaturesAdded );
1661 this, &QgsOfflineEditing::committedFeaturesRemoved );
1664void QgsOfflineEditing::setupLayer(
QgsMapLayer *layer )
1668 if (
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer ) )
1679int QgsOfflineEditing::getLayerPkIdx(
const QgsVectorLayer *layer )
const
1682 if ( pkAttrs.length() == 1 )
1685 const QMetaType::Type pkType = pkField.
type();
1687 if ( pkType == QMetaType::Type::QString )
1696QString QgsOfflineEditing::sqlEscape( QString value )
const
1698 if ( value.isNull() )
1699 return QStringLiteral(
"NULL" );
1701 value.replace(
"'",
"''" );
1703 return QStringLiteral(
"'%1'" ).arg( value );
@ Fids
Filter using feature IDs.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
WkbType
The WKB type describes the number of dimensions a geometry has.
@ MultiPolygon
MultiPolygon.
@ MultiLineString
MultiLineString.
virtual void invalidateConnections(const QString &connection)
Invalidate connections corresponding to specified name.
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)
Fetch next feature and stores in f, returns true on success.
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.
Qgis::FeatureRequestFilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
@ ConstraintNotNull
Field may not be null.
@ ConstraintUnique
Field must have a unique value.
Encapsulate a field in an attribute table or data source.
QMetaType::Type subType() const
If the field is a collection, gets its element's type.
void setTypeName(const QString &typeName)
Set the field type.
Container of fields for a vector layer.
Q_INVOKABLE int indexOf(const QString &fieldName) const
Gets the field index from the field name.
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
int fieldOriginIndex(int fieldIdx) const
Returns the field's origin index (its meaning is specific to each type of origin).
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
A geometry is the spatial representation of a feature.
static Q_INVOKABLE QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
Q_INVOKABLE QString asWkt(int precision=17) const
Exports the geometry to WKT.
static Q_INVOKABLE QString encodeValue(const QVariant &value)
Encodes a value to a JSON string representation, adding appropriate quotations and escaping where req...
static Q_INVOKABLE QVariantList parseArray(const QString &json, QMetaType::Type type=QMetaType::Type::UnknownType)
Parse a simple array (depth=1)
Base class for all map layer types.
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
QString source() const
Returns the source for the layer.
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
QString providerType() const
Returns the provider type (provider key) for this layer.
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
void editingStarted()
Emitted when editing on this layer has started.
QgsCoordinateReferenceSystem crs
void setDataSource(const QString &dataSource, const QString &baseName=QString(), const QString &provider=QString(), bool loadDefaultStyleFlag=false)
Updates the data source of the layer.
Q_INVOKABLE void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
void progressModeSet(QgsOfflineEditing::ProgressMode mode, long long maximum)
Emitted when the mode for the progress of the current operation is set.
void progressUpdated(long long progress)
Emitted with the progress of the current mode.
void layerProgressUpdated(int layer, int numLayers)
Emitted whenever a new layer is being processed.
bool isOfflineProject() const
Returns true if current project is offline.
bool convertToOfflineProject(const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds, bool onlySelected=false, ContainerType containerType=SpatiaLite, const QString &layerNameSuffix=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.
Stores queued vector layer edit operations prior to committing changes to the layer's data provider.
void committedAttributeValuesChanges(const QString &layerId, const QgsChangedAttributesMap &changedAttributesValues)
Emitted after feature attribute value changes have been committed to the layer.
void committedAttributesAdded(const QString &layerId, const QList< QgsField > &addedAttributes)
Emitted after attribute addition has been committed to the layer.
void committedGeometriesChanges(const QString &layerId, const QgsGeometryMap &changedGeometries)
Emitted after feature geometry changes have been committed to the layer.
static QgsFeature createFeature(const QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
Represents a vector layer which manages a vector based data sets.
void committedFeaturesAdded(const QString &layerId, const QgsFeatureList &addedFeatures)
Emitted when features are added to the provider if not in transaction mode.
Q_INVOKABLE QgsAttributeList attributeList() const
Returns list of attribute indexes.
Q_INVOKABLE bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false, QgsVectorLayerToolsContext *context=nullptr)
Changes an attribute value for a feature (but does not immediately commit the changes).
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.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
Q_INVOKABLE bool deleteFeature(QgsFeatureId fid, QgsVectorLayer::DeleteContext *context=nullptr)
Deletes a feature from the layer (but does not commit it).
void removeFieldConstraint(int index, QgsFieldConstraints::Constraint constraint)
Removes a constraint for a specified field index.
void committedFeaturesRemoved(const QString &layerId, const QgsFeatureIds &deletedFeatureIds)
Emitted when features are deleted from the provider if not in transaction mode.
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 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.