21#include <ogr_srs_api.h>
41#include <QDomDocument>
44#include <QRegularExpression>
47#include "moc_qgsofflineediting.cpp"
49using namespace Qt::StringLiterals;
59#include <spatialite.h>
63#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
64#define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
65#define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
66#define CUSTOM_SHOW_FEATURE_COUNT "showFeatureCount"
67#define CUSTOM_PROPERTY_ORIGINAL_LAYERID "remoteLayerId"
68#define CUSTOM_PROPERTY_LAYERNAME_SUFFIX "layerNameSuffix"
69#define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
70#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
96 const QString &offlineDataPath,
const QString &offlineDbFile,
const QStringList &layerIds,
bool onlySelected,
ContainerType containerType,
const QString &layerNameSuffix
99 if ( layerIds.isEmpty() )
104 const QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
105 if ( createOfflineDb( dbPath, containerType ) )
108 const int rc = database.
open( dbPath );
109 if ( rc != SQLITE_OK )
111 showWarning( tr(
"Could not open the SpatiaLite database" ) );
116 createLoggingTables( database.get() );
121 for (
int i = 0; i < layerIds.count(); i++ )
125 QgsMapLayer *layer = mProject->mapLayer( layerIds.at( i ) );
129 convertToOfflineLayer( vl, database.get(), dbPath, onlySelected, containerType, layerNameSuffix );
136 QString projectTitle = mProject->title();
137 if ( projectTitle.isEmpty() )
139 projectTitle = QFileInfo( mProject->fileName() ).fileName();
141 projectTitle +=
" (offline)"_L1;
142 mProject->setTitle( projectTitle );
171 QMap<QString, QgsMapLayer *> mapLayers = mProject->mapLayers();
172 QMap<int, std::shared_ptr<QgsVectorLayer>> remoteLayersByOfflineId;
173 QMap<int, QgsVectorLayer *> offlineLayersByOfflineId;
175 for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin(); layer_it != mapLayers.end(); ++layer_it )
177 QgsVectorLayer *offlineLayer( qobject_cast<QgsVectorLayer *>( layer_it.value() ) );
179 if ( !offlineLayer || !offlineLayer->
isValid() )
181 QgsDebugMsgLevel( u
"Skipping offline layer %1 because it is an invalid layer"_s.arg( layer_it.key() ), 4 );
190 QString remoteName = offlineLayer->
name();
192 if ( remoteName.endsWith( remoteNameSuffix ) )
193 remoteName.chop( remoteNameSuffix.size() );
196 auto remoteLayer = std::make_shared<QgsVectorLayer>( remoteSource, remoteName, remoteProvider, options );
198 if ( !remoteLayer->isValid() )
200 QgsDebugMsgLevel( u
"Skipping offline layer %1 because it failed to recreate its corresponding remote layer"_s.arg( offlineLayer->
id() ), 4 );
205 if ( remoteLayer->providerType().contains(
"WFS"_L1, Qt::CaseInsensitive ) )
216 const QString sql = u
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'"_s.arg( offlineLayer->
id() );
217 const int layerId = sqlQueryInt( database.get(), sql, -1 );
221 QgsDebugMsgLevel( u
"Skipping offline layer %1 because it failed to determine the offline editing layer id"_s.arg( offlineLayer->
id() ), 4 );
225 remoteLayersByOfflineId.insert( layerId, remoteLayer );
226 offlineLayersByOfflineId.insert( layerId, offlineLayer );
229 QgsDebugMsgLevel( u
"Found %1 offline layers in total"_s.arg( offlineLayersByOfflineId.count() ), 4 );
231 QMap<QPair<QString, QString>, std::shared_ptr<QgsTransactionGroup>> transactionGroups;
232 if ( useTransaction )
234 for (
const std::shared_ptr<QgsVectorLayer> &remoteLayer : std::as_const( remoteLayersByOfflineId ) )
237 const QPair<QString, QString> pair( remoteLayer->providerType(), connectionString );
238 std::shared_ptr<QgsTransactionGroup> transactionGroup = transactionGroups.value( pair );
240 if ( !transactionGroup )
241 transactionGroup = std::make_shared<QgsTransactionGroup>();
243 if ( !transactionGroup->addLayer( remoteLayer.get() ) )
245 QgsDebugMsgLevel( u
"Failed to add a layer %1 into transaction group, will be modified without transaction"_s.arg( remoteLayer->name() ), 4 );
249 transactionGroups.insert( pair, transactionGroup );
252 QgsDebugMsgLevel( u
"Created %1 transaction groups"_s.arg( transactionGroups.count() ), 4 );
255 const QList<int> offlineIds = remoteLayersByOfflineId.keys();
256 for (
int offlineLayerId : offlineIds )
258 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId.value( offlineLayerId );
259 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId.value( offlineLayerId );
262 QgsDebugMsgLevel( u
"Failed to find offline layer %1"_s.arg( offlineLayerId ), 4 );
267 if ( !remoteLayer->startEditing() && !remoteLayer->isEditable() )
269 QgsDebugMsgLevel( u
"Failed to turn layer %1 into editing mode"_s.arg( remoteLayer->name() ), 4 );
274 const int commitNo = getCommitNo( database.get() );
277 for (
int i = 0; i < commitNo; i++ )
281 applyAttributesAdded( remoteLayer.get(), database.get(), offlineLayerId, i );
282 applyAttributeValueChanges( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId, i );
283 applyGeometryChanges( remoteLayer.get(), database.get(), offlineLayerId, i );
286 applyFeaturesAdded( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId );
287 applyFeaturesRemoved( remoteLayer.get(), database.get(), offlineLayerId );
291 for (
int offlineLayerId : offlineIds )
293 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId[offlineLayerId];
294 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId[offlineLayerId];
296 if ( !remoteLayer->isEditable() )
299 if ( remoteLayer->commitChanges() )
302 updateFidLookup( remoteLayer.get(), database.get(), offlineLayerId );
306 sql = u
"DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
307 sqlExec( database.get(), sql );
308 sql = u
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
309 sqlExec( database.get(), sql );
310 sql = u
"DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
311 sqlExec( database.get(), sql );
312 sql = u
"DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
313 sqlExec( database.get(), sql );
314 sql = u
"DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1"_s.arg( offlineLayerId );
315 sqlExec( database.get(), sql );
319 showWarning( remoteLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
326 remoteLayer->reload();
327 offlineLayer->
setDataSource( remoteLayer->source(), remoteLayer->name(), remoteLayer->dataProvider()->name() );
343 const QgsFields fields = remoteLayer->fields();
344 for (
const QgsField &field : fields )
346 if ( !remoteLayer->dataProvider()->defaultValueClause( remoteLayer->fields().fieldOriginIndex( remoteLayer->fields().indexOf( field.name() ) ) ).isEmpty() )
354 const QString projectTitle = mProject->title().remove( QRegularExpression(
" \\(offline\\)$" ) );
355 mProject->setTitle( projectTitle );
358 const QString sql = u
"UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'"_s;
359 sqlExec( database.get(), sql );
363void QgsOfflineEditing::initializeSpatialMetadata(
sqlite3 *sqlite_handle )
365#ifdef HAVE_SPATIALITE
367 if ( !sqlite_handle )
370 char **results =
nullptr;
372 int ret = sqlite3_get_table( sqlite_handle,
"select count(*) from sqlite_master", &results, &rows, &columns,
nullptr );
373 if ( ret != SQLITE_OK )
378 for (
int i = 1; i <= rows; i++ )
379 count = atoi( results[( i * columns ) + 0] );
382 sqlite3_free_table( results );
387 bool above41 =
false;
388 ret = sqlite3_get_table( sqlite_handle,
"select spatialite_version()", &results, &rows, &columns,
nullptr );
389 if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
391 const QString version = QString::fromUtf8( results[1] );
392 const QStringList parts = version.split(
' ', Qt::SkipEmptyParts );
393 if ( !parts.empty() )
395 const QStringList verparts = parts.at( 0 ).split(
'.', Qt::SkipEmptyParts );
396 above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
400 sqlite3_free_table( results );
403 char *errMsg =
nullptr;
404 ret = sqlite3_exec( sqlite_handle, above41 ?
"SELECT InitSpatialMetadata(1)" :
"SELECT InitSpatialMetadata()",
nullptr,
nullptr, &errMsg );
406 if ( ret != SQLITE_OK )
408 QString errCause = tr(
"Unable to initialize SpatialMetadata:\n" );
409 errCause += QString::fromUtf8( errMsg );
410 showWarning( errCause );
411 sqlite3_free( errMsg );
414 spatial_ref_sys_init( sqlite_handle, 0 );
416 ( void ) sqlite_handle;
420bool QgsOfflineEditing::createOfflineDb(
const QString &offlineDbPath, ContainerType containerType )
423 char *errMsg =
nullptr;
424 const QFile newDb( offlineDbPath );
425 if ( newDb.exists() )
427 QFile::remove( offlineDbPath );
432 const QFileInfo fullPath = QFileInfo( offlineDbPath );
433 const QDir path = fullPath.dir();
436 QDir().mkpath( path.absolutePath() );
439 const QString dbPath = newDb.fileName();
442 switch ( containerType )
446 OGRSFDriverH hGpkgDriver = OGRGetDriverByName(
"GPKG" );
449 showWarning( tr(
"Creation of database failed. GeoPackage driver not found." ) );
456 showWarning( tr(
"Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
467 spatialite_database_unique_ptr database;
468 ret = database.
open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
nullptr );
472 QString errCause = tr(
"Could not create a new database\n" );
474 showWarning( errCause );
478 ret = sqlite3_exec( database.get(),
"PRAGMA foreign_keys = 1",
nullptr,
nullptr, &errMsg );
479 if ( ret != SQLITE_OK )
481 showWarning( tr(
"Unable to activate FOREIGN_KEY constraints" ) );
482 sqlite3_free( errMsg );
485 initializeSpatialMetadata( database.get() );
489void QgsOfflineEditing::createLoggingTables(
sqlite3 *db )
492 QString sql = u
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)"_s;
495 sql = u
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)"_s;
498 sql = u
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)"_s;
502 sql = u
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)"_s;
506 sql = u
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER, 'remote_pk' TEXT)"_s;
510 sql = u
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, "_s;
511 sql +=
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)"_L1;
515 sql = u
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)"_s;
519 sql = u
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)"_s;
523 sql = u
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)"_s;
527 sql = u
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)"_s;
535void QgsOfflineEditing::convertToOfflineLayer(
QgsVectorLayer *layer,
sqlite3 *db,
const QString &offlineDbPath,
bool onlySelected, ContainerType containerType,
const QString &layerNameSuffix )
537 if ( !layer || !layer->
isValid() )
539 QgsDebugMsgLevel( u
"Layer %1 is invalid and cannot be copied"_s.arg( layer ? layer->
id() : u
"<UNKNOWN>"_s ), 4 );
543 const QString tableName = layer->
id();
547 std::unique_ptr<QgsVectorLayer> newLayer;
549 switch ( containerType )
553#ifdef HAVE_SPATIALITE
555 QString sql = u
"CREATE TABLE '%1' ("_s.arg( tableName );
558 for (
const auto &field : providerFields )
561 const QMetaType::Type type = field.type();
562 if ( type == QMetaType::Type::Int || type == QMetaType::Type::LongLong )
564 dataType = u
"INTEGER"_s;
566 else if ( type == QMetaType::Type::Double )
568 dataType = u
"REAL"_s;
570 else if ( type == QMetaType::Type::QString )
572 dataType = u
"TEXT"_s;
574 else if ( type == QMetaType::Type::QStringList || type == QMetaType::Type::QVariantList )
576 dataType = u
"TEXT"_s;
577 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() ) );
581 showWarning( tr(
"%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
584 sql += delim + u
"'%1' %2"_s.arg( field.name(), dataType );
589 int rc = sqlExec( db, sql );
600 geomType = u
"POINT"_s;
603 geomType = u
"MULTIPOINT"_s;
606 geomType = u
"LINESTRING"_s;
609 geomType = u
"MULTILINESTRING"_s;
612 geomType = u
"POLYGON"_s;
615 geomType = u
"MULTIPOLYGON"_s;
622 QString zmInfo = u
"XY"_s;
631 if ( layer->
crs().
authid().startsWith(
"EPSG:"_L1, Qt::CaseInsensitive ) )
633 epsgCode = layer->
crs().
authid().mid( 5 );
638 showWarning( tr(
"Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->
name(), layer->
crs().
authid() ) );
641 const QString sqlAddGeom = u
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')"_s.arg( tableName, epsgCode, geomType, zmInfo );
644 const QString sqlCreateIndex = u
"SELECT CreateSpatialIndex('%1', 'Geometry')"_s.arg( tableName );
646 if ( rc == SQLITE_OK )
648 rc = sqlExec( db, sqlAddGeom );
649 if ( rc == SQLITE_OK )
651 rc = sqlExec( db, sqlCreateIndex );
656 if ( rc != SQLITE_OK )
658 showWarning( tr(
"Filling SpatiaLite for layer %1 failed" ).arg( layer->
name() ) );
663 const QString connectionString = u
"dbname='%1' table='%2'%3 sql="_s.arg( offlineDbPath, tableName, layer->
isSpatial() ?
"(Geometry)" :
"" );
664 const QgsVectorLayer::LayerOptions options { mProject->transformContext() };
665 newLayer = std::make_unique<QgsVectorLayer>( connectionString, layer->
name() + layerNameSuffix, u
"spatialite"_s, options );
669 showWarning( tr(
"No Spatialite support available" ) );
677 char **options =
nullptr;
679 options = CSLSetNameValue( options,
"OVERWRITE",
"YES" );
680 options = CSLSetNameValue( options,
"IDENTIFIER", tr(
"%1 (offline)" ).arg( layer->
id() ).toUtf8().constData() );
681 options = CSLSetNameValue( options,
"DESCRIPTION", layer->
dataComment().toUtf8().constData() );
684 const QString fidBase( u
"fid"_s );
685 QString fid = fidBase;
689 fid = fidBase +
'_' + QString::number( counter );
692 if ( counter == 10000 )
694 showWarning( tr(
"Cannot make FID-name for GPKG " ) );
698 options = CSLSetNameValue( options,
"FID", fid.toUtf8().constData() );
702 options = CSLSetNameValue( options,
"GEOMETRY_COLUMN",
"geom" );
703 options = CSLSetNameValue( options,
"SPATIAL_INDEX",
"YES" );
706 OGRSFDriverH hDriver =
nullptr;
709 OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS,
static_cast<OGRwkbGeometryType
>( layer->
wkbType() ), options );
710 CSLDestroy( options );
715 showWarning( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
720 for (
const auto &field : providerFields )
722 const QString fieldName( field.name() );
723 const QMetaType::Type type = field.type();
724 OGRFieldType ogrType( OFTString );
725 OGRFieldSubType ogrSubType = OFSTNone;
726 if ( type == QMetaType::Type::Int )
727 ogrType = OFTInteger;
728 else if ( type == QMetaType::Type::LongLong )
729 ogrType = OFTInteger64;
730 else if ( type == QMetaType::Type::Double )
732 else if ( type == QMetaType::Type::QTime )
734 else if ( type == QMetaType::Type::QDate )
736 else if ( type == QMetaType::Type::QDateTime )
737 ogrType = OFTDateTime;
738 else if ( type == QMetaType::Type::Bool )
740 ogrType = OFTInteger;
741 ogrSubType = OFSTBoolean;
743 else if ( type == QMetaType::Type::QStringList || type == QMetaType::Type::QVariantList )
746 ogrSubType = OFSTJSON;
747 showWarning( tr(
"Field '%1' from layer %2 has been converted from a list to a JSON-formatted string value." ).arg( fieldName, layer->
name() ) );
752 const int ogrWidth = field.length();
755 OGR_Fld_SetWidth( fld.get(), ogrWidth );
756 if ( ogrSubType != OFSTNone )
757 OGR_Fld_SetSubType( fld.get(), ogrSubType );
759 if ( OGR_L_CreateField( hLayer, fld.get(),
true ) != OGRERR_NONE )
761 showWarning( tr(
"Creation of field %1 failed (OGR error: %2)" ).arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
769 OGR_L_ResetReading( hLayer );
770 if ( CPLGetLastErrorType() != CE_None )
772 const QString msg( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
778 const QString uri = u
"%1|layername=%2|option:QGIS_FORCE_WAL=ON"_s.arg( offlineDbPath, tableName );
779 const QgsVectorLayer::LayerOptions layerOptions { mProject->transformContext() };
780 newLayer = std::make_unique<QgsVectorLayer>( uri, layer->
name() + layerNameSuffix, u
"ogr"_s, layerOptions );
785 if ( newLayer && newLayer->isValid() )
788 newLayer->startEditing();
791 QgsFeatureRequest req;
796 if ( !selectedFids.isEmpty() )
810 long long featureCount = 1;
811 const int remotePkIdx = getLayerPkIdx( layer );
813 QList<QgsFeatureId> remoteFeatureIds;
814 QStringList remoteFeaturePks;
817 remoteFeatureIds << f.
id();
818 remoteFeaturePks << ( remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString() );
825 QgsAttributes newAttrs( containerType ==
GPKG ? attrs.count() + 1 : attrs.count() );
826 for (
int it = 0; it < attrs.count(); ++it )
828 const QVariant attr = attrs.at( it );
829 newAttrs[column++] = attr;
833 newLayer->addFeature( f );
837 if ( newLayer->commitChanges() )
843 const int layerId = getOrCreateLayerId( db, layer->
id() );
844 QList<QgsFeatureId> offlineFeatureIds;
849 offlineFeatureIds << f.
id();
853 sqlExec( db, u
"BEGIN"_s );
854 const int remoteCount = remoteFeatureIds.size();
855 for (
int i = 0; i < remoteCount; i++ )
858 if ( i < offlineFeatureIds.count() )
860 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ), remoteFeaturePks.at( i ) );
864 showWarning( tr(
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
869 sqlExec( db, u
"COMMIT"_s );
873 showWarning( newLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
886 const QgsFields fields = layer->
fields();
887 QStringList notNullFieldNames;
888 for (
const QgsField &field : fields )
892 notNullFieldNames << field.name();
896 layer->
setDataSource( newLayer->source(), newLayer->name(), newLayer->dataProvider()->name() );
898 for (
const QgsField &field : fields )
908 if ( notNullFieldNames.contains( field.name() ) )
910 notNullFieldNames.removeAll( field.name() );
921void QgsOfflineEditing::applyAttributesAdded(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
923 Q_ASSERT( remoteLayer );
925 const QString sql = u
"SELECT \"name\", \"type\", \"length\", \"precision\", \"comment\" FROM 'log_added_attrs' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2"_s.arg( layerId ).arg( commitNo );
926 QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
928 const QgsVectorDataProvider *provider = remoteLayer->
dataProvider();
929 const QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
932 QMap< QMetaType::Type, QString > typeNameLookup;
933 for (
int i = 0; i < nativeTypes.size(); i++ )
935 const QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i );
941 for (
int i = 0; i < fields.size(); i++ )
944 QgsField field = fields[i];
945 if ( typeNameLookup.contains( field.
type() ) )
947 const QString typeName = typeNameLookup[field.
type()];
953 showWarning( u
"Could not add attribute '%1' of type %2"_s.arg( field.
name() ).arg( field.
type() ) );
962 Q_ASSERT( offlineLayer );
963 Q_ASSERT( remoteLayer );
965 const QString sql = u
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1"_s.arg( layerId );
966 const QList<int> featureIdInts = sqlQueryInts( db, sql );
968 for (
const int id : featureIdInts )
977 QgsFeatureIterator it = offlineLayer->
getFeatures( QgsFeatureRequest().setFilterFids( newFeatureIds ) );
988 const int newAttrsCount = remoteLayer->
fields().
count();
989 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
993 const QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
994 QgsAttributes newAttrs( newAttrsCount );
995 const QgsAttributes attrs = it->attributes();
996 for (
int it = 0; it < attrs.count(); ++it )
998 const int remoteAttributeIndex = attrLookup.value( it, -1 );
1000 if ( remoteAttributeIndex == -1 )
1002 QVariant attr = attrs.at( it );
1003 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QStringList )
1005 if ( attr.userType() == QMetaType::Type::QStringList || attr.userType() == QMetaType::Type::QVariantList )
1007 attr = attr.toStringList();
1014 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QVariantList )
1016 if ( attr.userType() == QMetaType::Type::QStringList || attr.userType() == QMetaType::Type::QVariantList )
1018 attr = attr.toList();
1025 newAttrs[remoteAttributeIndex] = attr;
1036void QgsOfflineEditing::applyFeaturesRemoved(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId )
1038 Q_ASSERT( remoteLayer );
1040 const QString sql = u
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1"_s.arg( layerId );
1041 const QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
1046 for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
1048 const QgsFeatureId fid = remoteFid( db, layerId, *it, remoteLayer );
1057 Q_ASSERT( offlineLayer );
1058 Q_ASSERT( remoteLayer );
1060 const QString sql = u
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 "_s.arg( layerId ).arg( commitNo );
1061 const AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
1065 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1067 for (
int i = 0; i < values.size(); i++ )
1069 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1070 QgsDebugMsgLevel( u
"Offline changeAttributeValue %1 = %2"_s.arg( attrLookup[values.at( i ).attr] ).arg( values.at( i ).value ), 4 );
1072 const int remoteAttributeIndex = attrLookup[values.at( i ).attr];
1073 QVariant attr = values.at( i ).value;
1074 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QStringList )
1078 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QVariantList )
1089void QgsOfflineEditing::applyGeometryChanges(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
1091 Q_ASSERT( remoteLayer );
1093 const QString sql = u
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2"_s.arg( layerId ).arg( commitNo );
1094 const GeometryChanges values = sqlQueryGeometryChanges( db, sql );
1098 for (
int i = 0; i < values.size(); i++ )
1100 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1110 Q_ASSERT( remoteLayer );
1116 QMap< QgsFeatureId, QString > newRemoteFids;
1123 const int remotePkIdx = getLayerPkIdx( remoteLayer );
1128 if ( offlineFid( db, layerId, f.
id() ) == -1 )
1130 newRemoteFids[f.
id()] = remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString();
1138 const QString sql = u
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1"_s.arg( layerId );
1139 const QList<int> newOfflineFids = sqlQueryInts( db, sql );
1141 if ( newRemoteFids.size() != newOfflineFids.size() )
1149 sqlExec( db, u
"BEGIN"_s );
1150 for ( QMap<QgsFeatureId, QString>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
1152 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key(), it.value() );
1154 sqlExec( db, u
"COMMIT"_s );
1161 Q_ASSERT( offlineLayer );
1162 Q_ASSERT( remoteLayer );
1166 QMap<
int ,
int > attrLookup;
1169 for (
int i = 0; i < offlineAttrs.size(); i++ )
1178void QgsOfflineEditing::showWarning(
const QString &message )
1180 emit
warning( tr(
"Offline Editing Plugin" ), message );
1185 sqlite3_database_unique_ptr database;
1187 if ( !dbPath.isEmpty() )
1189 const QString absoluteDbPath = mProject->readPath( dbPath );
1190 const int rc = database.
open( absoluteDbPath );
1191 if ( rc != SQLITE_OK )
1193 QgsDebugError( u
"Could not open the SpatiaLite logging database"_s );
1194 showWarning( tr(
"Could not open the SpatiaLite logging database" ) );
1204int QgsOfflineEditing::getOrCreateLayerId(
sqlite3 *db,
const QString &qgisLayerId )
1206 QString sql = u
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'"_s.arg( qgisLayerId );
1207 int layerId = sqlQueryInt( db, sql, -1 );
1208 if ( layerId == -1 )
1211 sql = u
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'"_s;
1212 const int newLayerId = sqlQueryInt( db, sql, -1 );
1215 sql = u
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')"_s.arg( newLayerId ).arg( qgisLayerId );
1220 sql = u
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'"_s.arg( newLayerId + 1 );
1223 layerId = newLayerId;
1229int QgsOfflineEditing::getCommitNo(
sqlite3 *db )
1231 const QString sql = u
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'"_s;
1232 return sqlQueryInt( db, sql, -1 );
1235void QgsOfflineEditing::increaseCommitNo(
sqlite3 *db )
1237 const QString sql = u
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'"_s.arg( getCommitNo( db ) + 1 );
1243 const QString sql = u
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3, %4 )"_s.arg( layerId ).arg( offlineFid ).arg( remoteFid ).arg( sqlEscape( remotePk ) );
1249 const int pkIdx = getLayerPkIdx( remoteLayer );
1253 const QString sql = u
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2"_s.arg( layerId ).arg( offlineFid );
1254 return sqlQueryInt( db, sql, -1 );
1257 const QString sql = u
"SELECT \"remote_pk\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2"_s.arg( layerId ).arg( offlineFid );
1258 QString defaultValue;
1259 const QString pkValue = sqlQueryStr( db, sql, defaultValue );
1261 if ( pkValue.isNull() )
1266 const QString pkFieldName = remoteLayer->
fields().
at( pkIdx ).
name();
1267 QgsFeatureIterator fit = remoteLayer->
getFeatures( u
" %1 = %2 "_s.arg( pkFieldName ).arg( sqlEscape( pkValue ) ) );
1277 const QString sql = u
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2"_s.arg( layerId ).arg( remoteFid );
1278 return sqlQueryInt( db, sql, -1 );
1283 const QString sql = u
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2"_s.arg( layerId ).arg( fid );
1284 return ( sqlQueryInt( db, sql, 0 ) > 0 );
1287int QgsOfflineEditing::sqlExec(
sqlite3 *db,
const QString &sql )
1289 char *errmsg =
nullptr;
1290 const int rc = sqlite3_exec( db, sql.toUtf8(),
nullptr,
nullptr, &errmsg );
1291 if ( rc != SQLITE_OK )
1293 showWarning( errmsg );
1298QString QgsOfflineEditing::sqlQueryStr(
sqlite3 *db,
const QString &sql, QString &defaultValue )
1300 sqlite3_stmt *stmt =
nullptr;
1301 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1303 showWarning( sqlite3_errmsg( db ) );
1304 return defaultValue;
1307 QString value = defaultValue;
1308 const int ret = sqlite3_step( stmt );
1309 if ( ret == SQLITE_ROW )
1311 value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) );
1313 sqlite3_finalize( stmt );
1318int QgsOfflineEditing::sqlQueryInt(
sqlite3 *db,
const QString &sql,
int defaultValue )
1320 sqlite3_stmt *stmt =
nullptr;
1321 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1323 showWarning( sqlite3_errmsg( db ) );
1324 return defaultValue;
1327 int value = defaultValue;
1328 const int ret = sqlite3_step( stmt );
1329 if ( ret == SQLITE_ROW )
1331 value = sqlite3_column_int( stmt, 0 );
1333 sqlite3_finalize( stmt );
1338QList<int> QgsOfflineEditing::sqlQueryInts(
sqlite3 *db,
const QString &sql )
1342 sqlite3_stmt *stmt =
nullptr;
1343 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1345 showWarning( sqlite3_errmsg( db ) );
1349 int ret = sqlite3_step( stmt );
1350 while ( ret == SQLITE_ROW )
1352 values << sqlite3_column_int( stmt, 0 );
1354 ret = sqlite3_step( stmt );
1356 sqlite3_finalize( stmt );
1361QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded(
sqlite3 *db,
const QString &sql )
1363 QList<QgsField> values;
1365 sqlite3_stmt *stmt =
nullptr;
1366 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1368 showWarning( sqlite3_errmsg( db ) );
1372 int ret = sqlite3_step( stmt );
1373 while ( ret == SQLITE_ROW )
1375 const QgsField field(
1376 QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) ),
1377 static_cast< QMetaType::Type
>( sqlite3_column_int( stmt, 1 ) ),
1379 sqlite3_column_int( stmt, 2 ),
1380 sqlite3_column_int( stmt, 3 ),
1381 QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 4 ) ) )
1385 ret = sqlite3_step( stmt );
1387 sqlite3_finalize( stmt );
1396 sqlite3_stmt *stmt =
nullptr;
1397 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1399 showWarning( sqlite3_errmsg( db ) );
1403 int ret = sqlite3_step( stmt );
1404 while ( ret == SQLITE_ROW )
1406 values << sqlite3_column_int( stmt, 0 );
1408 ret = sqlite3_step( stmt );
1410 sqlite3_finalize( stmt );
1415QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges(
sqlite3 *db,
const QString &sql )
1417 AttributeValueChanges values;
1419 sqlite3_stmt *stmt =
nullptr;
1420 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1422 showWarning( sqlite3_errmsg( db ) );
1426 int ret = sqlite3_step( stmt );
1427 while ( ret == SQLITE_ROW )
1429 AttributeValueChange change;
1430 change.fid = sqlite3_column_int( stmt, 0 );
1431 change.attr = sqlite3_column_int( stmt, 1 );
1432 change.value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 2 ) ) );
1435 ret = sqlite3_step( stmt );
1437 sqlite3_finalize( stmt );
1442QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges(
sqlite3 *db,
const QString &sql )
1444 GeometryChanges values;
1446 sqlite3_stmt *stmt =
nullptr;
1447 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1449 showWarning( sqlite3_errmsg( db ) );
1453 int ret = sqlite3_step( stmt );
1454 while ( ret == SQLITE_ROW )
1456 GeometryChange change;
1457 change.fid = sqlite3_column_int( stmt, 0 );
1458 change.geom_wkt = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 1 ) ) );
1461 ret = sqlite3_step( stmt );
1463 sqlite3_finalize( stmt );
1468void QgsOfflineEditing::committedAttributesAdded(
const QString &qgisLayerId,
const QList<QgsField> &addedAttributes )
1470 const sqlite3_database_unique_ptr database = openLoggingDb();
1475 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1476 const int commitNo = getCommitNo( database.get() );
1478 for (
const QgsField &field : addedAttributes )
1480 const QString sql = u
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )"_s.arg( layerId )
1482 .arg( field.
name() )
1483 .arg( field.
type() )
1487 sqlExec( database.get(), sql );
1490 increaseCommitNo( database.get() );
1493void QgsOfflineEditing::committedFeaturesAdded(
const QString &qgisLayerId,
const QgsFeatureList &addedFeatures )
1495 const sqlite3_database_unique_ptr database = openLoggingDb();
1500 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1503 QgsMapLayer *layer = mProject->mapLayer( qgisLayerId );
1504 const QString dataSourceString = layer->
source();
1505 const QgsDataSourceUri uri = QgsDataSourceUri( dataSourceString );
1510 if ( !offlinePath.contains(
".gpkg" ) )
1512 tableName = uri.
table();
1517 const QVariantMap decodedUri = ogrProviderMetaData->
decodeUri( dataSourceString );
1518 tableName = decodedUri.value( u
"layerName"_s ).toString();
1519 if ( tableName.isEmpty() )
1521 showWarning( tr(
"Could not deduce table name from data source %1." ).arg( dataSourceString ) );
1526 const QString sql = u
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2"_s.arg( tableName ).arg( addedFeatures.size() );
1527 const QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1528 for (
int i = newFeatureIds.size() - 1; i >= 0; i-- )
1530 const QString sql = u
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )"_s.arg( layerId ).arg( newFeatureIds.at( i ) );
1531 sqlExec( database.get(), sql );
1535void QgsOfflineEditing::committedFeaturesRemoved(
const QString &qgisLayerId,
const QgsFeatureIds &deletedFeatureIds )
1537 const sqlite3_database_unique_ptr database = openLoggingDb();
1542 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1546 if ( isAddedFeature( database.get(), layerId,
id ) )
1549 const QString sql = u
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2"_s.arg( layerId ).arg(
id );
1550 sqlExec( database.get(), sql );
1554 const QString sql = u
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)"_s.arg( layerId ).arg(
id );
1555 sqlExec( database.get(), sql );
1560void QgsOfflineEditing::committedAttributeValuesChanges(
const QString &qgisLayerId,
const QgsChangedAttributesMap &changedAttrsMap )
1562 const sqlite3_database_unique_ptr database = openLoggingDb();
1567 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1568 const int commitNo = getCommitNo( database.get() );
1570 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1573 if ( isAddedFeature( database.get(), layerId, fid ) )
1579 for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1581 QString value = it.value().userType() == QMetaType::Type::QStringList || it.value().userType() == QMetaType::Type::QVariantList ?
QgsJsonUtils::encodeValue( it.value() ) : it.value().toString();
1582 value.replace(
"'"_L1,
"''"_L1 );
1583 const QString sql = u
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )"_s.arg( layerId )
1588 sqlExec( database.get(), sql );
1592 increaseCommitNo( database.get() );
1595void QgsOfflineEditing::committedGeometriesChanges(
const QString &qgisLayerId,
const QgsGeometryMap &changedGeometries )
1597 const sqlite3_database_unique_ptr database = openLoggingDb();
1602 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1603 const int commitNo = getCommitNo( database.get() );
1605 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1608 if ( isAddedFeature( database.get(), layerId, fid ) )
1613 const QgsGeometry geom = it.value();
1614 const QString sql = u
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )"_s.arg( layerId ).arg( commitNo ).arg( fid ).arg( geom.
asWkt() );
1615 sqlExec( database.get(), sql );
1620 increaseCommitNo( database.get() );
1623void QgsOfflineEditing::startListenFeatureChanges()
1625 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1632 QgsVectorLayerEditBuffer *editBuffer = vLayer->
editBuffer();
1641void QgsOfflineEditing::stopListenFeatureChanges()
1643 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1650 QgsVectorLayerEditBuffer *editBuffer = vLayer->
editBuffer();
1659void QgsOfflineEditing::setupLayer(
QgsMapLayer *layer )
1663 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer ) )
1674int QgsOfflineEditing::getLayerPkIdx(
const QgsVectorLayer *layer )
const
1677 if ( pkAttrs.length() == 1 )
1679 const QgsField pkField = layer->
fields().
at( pkAttrs[0] );
1680 const QMetaType::Type pkType = pkField.
type();
1682 if ( pkType == QMetaType::Type::QString )
1691QString QgsOfflineEditing::sqlEscape( QString value )
const
1693 if ( value.isNull() )
1696 value.replace(
"'",
"''" );
1698 return u
"'%1'"_s.arg( value );
@ Fids
Filter using feature IDs.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
WkbType
The WKB type describes the number of dimensions a geometry has.
@ MultiPolygon
MultiPolygon.
@ MultiLineString
MultiLineString.
virtual void invalidateConnections(const QString &connection)
Invalidate connections corresponding to specified name.
Stores the component parts of a data source URI (e.g.
QString table() const
Returns the table name stored in the URI.
QString database() const
Returns the database name stored in the URI.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
Qgis::FeatureRequestFilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
@ ConstraintNotNull
Field may not be null.
@ ConstraintUnique
Field must have a unique value.
Encapsulate a field in an attribute table or data source.
QMetaType::Type subType() const
If the field is a collection, gets its element's type.
void setTypeName(const QString &typeName)
Set the field type.
Container of fields for a vector layer.
Q_INVOKABLE int indexOf(const QString &fieldName) const
Gets the field index from the field name.
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
int fieldOriginIndex(int fieldIdx) const
Returns the field's origin index (its meaning is specific to each type of origin).
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
static Q_INVOKABLE QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
Q_INVOKABLE QString asWkt(int precision=17) const
Exports the geometry to WKT.
static Q_INVOKABLE QString encodeValue(const QVariant &value)
Encodes a value to a JSON string representation, adding appropriate quotations and escaping where req...
static Q_INVOKABLE QVariantList parseArray(const QString &json, QMetaType::Type type=QMetaType::Type::UnknownType)
Parse a simple array (depth=1).
Base class for all map layer types.
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
QString source() const
Returns the source for the layer.
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
QString providerType() const
Returns the provider type (provider key) for this layer.
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
void editingStarted()
Emitted when editing on this layer has started.
QgsCoordinateReferenceSystem crs
void setDataSource(const QString &dataSource, const QString &baseName=QString(), const QString &provider=QString(), bool loadDefaultStyleFlag=false)
Updates the data source of the layer.
Q_INVOKABLE void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
void progressModeSet(QgsOfflineEditing::ProgressMode mode, long long maximum)
Emitted when the mode for the progress of the current operation is set.
void progressUpdated(long long progress)
Emitted with the progress of the current mode.
void layerProgressUpdated(int layer, int numLayers)
Emitted whenever a new layer is being processed.
bool isOfflineProject() const
Returns true if current project is offline.
QgsOfflineEditing()
Default constructor – uses the QgsProject instance().
bool convertToOfflineProject(const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds, bool onlySelected=false, ContainerType containerType=SpatiaLite, const QString &layerNameSuffix=u" (offline)"_s)
Convert current project for offline editing.
void warning(const QString &title, const QString &message)
Emitted when a warning needs to be displayed.
void progressStopped()
Emitted when the processing of all layers has finished.
void synchronize(bool useTransaction=false)
Synchronize to remote layers.
ContainerType
Type of offline database container file.
void progressStarted()
Emitted when the process has started.
static OGRSpatialReferenceH crsToOGRSpatialReference(const QgsCoordinateReferenceSystem &crs)
Returns a OGRSpatialReferenceH corresponding to the specified crs object.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
void layerWasAdded(QgsMapLayer *layer)
Emitted when a layer was added to the registry.
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
Stores configuration of snapping settings for the project.
QString connectionString() const
Returns the connection string of the transaction.
long long featureCount() const override=0
Number of features in the layer.
QList< QgsVectorDataProvider::NativeType > nativeTypes() const
Returns the names of the supported types.
virtual QString defaultValueClause(int fieldIndex) const
Returns any default value clauses which are present at the provider for a specified field index.
QgsFields fields() const override=0
Returns the fields associated with this data provider.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override=0
Query the provider for features specified in request.
void committedAttributeValuesChanges(const QString &layerId, const QgsChangedAttributesMap &changedAttributesValues)
Emitted after feature attribute value changes have been committed to the layer.
void committedAttributesAdded(const QString &layerId, const QList< QgsField > &addedAttributes)
Emitted after attribute addition has been committed to the layer.
void committedGeometriesChanges(const QString &layerId, const QgsGeometryMap &changedGeometries)
Emitted after feature geometry changes have been committed to the layer.
static QgsFeature createFeature(const QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
Represents a vector layer which manages a vector based dataset.
void committedFeaturesAdded(const QString &layerId, const QgsFeatureList &addedFeatures)
Emitted when features are added to the provider if not in transaction mode.
Q_INVOKABLE QgsAttributeList attributeList() const
Returns list of attribute indexes.
QgsExpressionContext createExpressionContext() const final
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Q_INVOKABLE bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false, QgsVectorLayerToolsContext *context=nullptr)
Changes an attribute value for a feature (but does not immediately commit the changes).
Q_INVOKABLE bool addAttribute(const QgsField &field)
Add an attribute field (but does not commit it) returns true if the field was added.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
void setFieldConstraint(int index, QgsFieldConstraints::Constraint constraint, QgsFieldConstraints::ConstraintStrength strength=QgsFieldConstraints::ConstraintStrengthHard)
Sets a constraint for a specified field index.
bool isSpatial() const final
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
Q_INVOKABLE bool deleteFeature(QgsFeatureId fid, QgsVectorLayer::DeleteContext *context=nullptr)
Deletes a feature from the layer (but does not commit it).
void removeFieldConstraint(int index, QgsFieldConstraints::Constraint constraint)
Removes a constraint for a specified field index.
void committedFeaturesRemoved(const QString &layerId, const QgsFeatureIds &deletedFeatureIds)
Emitted when features are deleted from the provider if not in transaction mode.
Q_INVOKABLE Qgis::WkbType wkbType() const final
Returns the WKBType or WKBUnknown in case of error.
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
QString dataComment() const
Returns a description for this layer as defined in the data provider.
Q_INVOKABLE QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
QgsAttributeList primaryKeyAttributes() const
Returns the list of attributes which make up the layer's primary keys.
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) final
Adds a single feature to the sink.
QgsVectorDataProvider * dataProvider() final
Returns the layer's data provider, it may be nullptr.
bool changeGeometry(QgsFeatureId fid, QgsGeometry &geometry, bool skipDefaultValue=false)
Changes a feature's geometry within the layer's edit buffer (but does not immediately commit the chan...
static Q_INVOKABLE QString displayString(Qgis::WkbType type)
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
Unique pointer for spatialite databases, which automatically closes the database when the pointer goe...
int open(const QString &path)
Opens the database at the specified file path.
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
QString errorMessage() const
Returns the most recent error message encountered by the database.
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
int open(const QString &path)
Opens the database at the specified file path.
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.
std::unique_ptr< std::remove_pointer< OGRFieldDefnH >::type, OGRFldDeleter > ogr_field_def_unique_ptr
Scoped OGR field definition.
QMap< int, QVariant > QgsAttributeMap
QMap< QgsFeatureId, QgsGeometry > QgsGeometryMap
QMap< QgsFeatureId, QgsAttributeMap > QgsChangedAttributesMap
QList< QgsFeature > QgsFeatureList
QSet< QgsFeatureId > QgsFeatureIds
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
QList< int > QgsAttributeList
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)
#define CUSTOM_PROPERTY_ORIGINAL_LAYERID
#define PROJECT_ENTRY_SCOPE_OFFLINE
#define CUSTOM_PROPERTY_REMOTE_PROVIDER
#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE
#define CUSTOM_PROPERTY_LAYERNAME_SUFFIX
#define CUSTOM_PROPERTY_REMOTE_SOURCE
#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH
Setting options for loading vector layers.