23#include "moc_qgsofflineediting.cpp"
39#include <QDomDocument>
42#include <QRegularExpression>
44#include <ogr_srs_api.h>
54#include <spatialite.h>
58#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
59#define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
60#define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
61#define CUSTOM_SHOW_FEATURE_COUNT "showFeatureCount"
62#define CUSTOM_PROPERTY_ORIGINAL_LAYERID "remoteLayerId"
63#define CUSTOM_PROPERTY_LAYERNAME_SUFFIX "layerNameSuffix"
64#define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
65#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
86 if ( layerIds.isEmpty() )
91 const QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
92 if ( createOfflineDb( dbPath, containerType ) )
95 const int rc = database.
open( dbPath );
96 if ( rc != SQLITE_OK )
98 showWarning( tr(
"Could not open the SpatiaLite database" ) );
103 createLoggingTables( database.get() );
108 for (
int i = 0; i < layerIds.count(); i++ )
116 convertToOfflineLayer( vl, database.get(), dbPath, onlySelected, containerType, layerNameSuffix );
124 if ( projectTitle.isEmpty() )
128 projectTitle += QLatin1String(
" (offline)" );
159 QMap<int, std::shared_ptr<QgsVectorLayer>> remoteLayersByOfflineId;
160 QMap<int, QgsVectorLayer *> offlineLayersByOfflineId;
162 for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
164 QgsVectorLayer *offlineLayer( qobject_cast<QgsVectorLayer *>( layer_it.value() ) );
166 if ( !offlineLayer || !offlineLayer->
isValid() )
168 QgsDebugMsgLevel( QStringLiteral(
"Skipping offline layer %1 because it is an invalid layer" ).arg( layer_it.key() ), 4 );
177 QString remoteName = offlineLayer->
name();
179 if ( remoteName.endsWith( remoteNameSuffix ) )
180 remoteName.chop( remoteNameSuffix.size() );
183 std::shared_ptr<QgsVectorLayer> remoteLayer = std::make_shared<QgsVectorLayer>( remoteSource, remoteName, remoteProvider, options );
185 if ( ! remoteLayer->isValid() )
187 QgsDebugMsgLevel( QStringLiteral(
"Skipping offline layer %1 because it failed to recreate its corresponding remote layer" ).arg( offlineLayer->
id() ), 4 );
192 if ( remoteLayer->providerType().contains( QLatin1String(
"WFS" ), Qt::CaseInsensitive ) )
203 const QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( offlineLayer->
id() );
204 const int layerId = sqlQueryInt( database.get(), sql, -1 );
208 QgsDebugMsgLevel( QStringLiteral(
"Skipping offline layer %1 because it failed to determine the offline editing layer id" ).arg( offlineLayer->
id() ), 4 );
212 remoteLayersByOfflineId.insert( layerId, remoteLayer );
213 offlineLayersByOfflineId.insert( layerId, offlineLayer );
216 QgsDebugMsgLevel( QStringLiteral(
"Found %1 offline layers in total" ).arg( offlineLayersByOfflineId.count() ), 4 );
218 QMap<QPair<QString, QString>, std::shared_ptr<QgsTransactionGroup>> transactionGroups;
219 if ( useTransaction )
221 for (
const std::shared_ptr<QgsVectorLayer> &remoteLayer : std::as_const( remoteLayersByOfflineId ) )
224 const QPair<QString, QString> pair( remoteLayer->providerType(), connectionString );
225 std::shared_ptr<QgsTransactionGroup> transactionGroup = transactionGroups.value( pair );
227 if ( !transactionGroup.get() )
228 transactionGroup = std::make_shared<QgsTransactionGroup>();
230 if ( !transactionGroup->addLayer( remoteLayer.get() ) )
232 QgsDebugMsgLevel( QStringLiteral(
"Failed to add a layer %1 into transaction group, will be modified without transaction" ).arg( remoteLayer->name() ), 4 );
236 transactionGroups.insert( pair, transactionGroup );
239 QgsDebugMsgLevel( QStringLiteral(
"Created %1 transaction groups" ).arg( transactionGroups.count() ), 4 );
242 const QList<int> offlineIds = remoteLayersByOfflineId.keys();
243 for (
int offlineLayerId : offlineIds )
245 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId.value( offlineLayerId );
246 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId.value( offlineLayerId );
249 if ( !remoteLayer->startEditing() && !remoteLayer->isEditable() )
251 QgsDebugMsgLevel( QStringLiteral(
"Failed to turn layer %1 into editing mode" ).arg( remoteLayer->name() ), 4 );
256 const int commitNo = getCommitNo( database.get() );
257 QgsDebugMsgLevel( QStringLiteral(
"Found %1 commits" ).arg( commitNo ), 4 );
259 for (
int i = 0; i < commitNo; i++ )
261 QgsDebugMsgLevel( QStringLiteral(
"Apply commits chronologically from %1" ).arg( offlineLayer->
name() ), 4 );
263 applyAttributesAdded( remoteLayer.get(), database.get(), offlineLayerId, i );
264 applyAttributeValueChanges( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId, i );
265 applyGeometryChanges( remoteLayer.get(), database.get(), offlineLayerId, i );
268 applyFeaturesAdded( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId );
269 applyFeaturesRemoved( remoteLayer.get(), database.get(), offlineLayerId );
273 for (
int offlineLayerId : offlineIds )
275 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId[offlineLayerId];
276 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId[offlineLayerId];
278 if ( !remoteLayer->isEditable() )
281 if ( remoteLayer->commitChanges() )
284 updateFidLookup( remoteLayer.get(), database.get(), offlineLayerId );
288 sql = QStringLiteral(
"DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
289 sqlExec( database.get(), sql );
290 sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
291 sqlExec( database.get(), sql );
292 sql = QStringLiteral(
"DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
293 sqlExec( database.get(), sql );
294 sql = QStringLiteral(
"DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
295 sqlExec( database.get(), sql );
296 sql = QStringLiteral(
"DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
297 sqlExec( database.get(), sql );
301 showWarning( remoteLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
308 remoteLayer->reload();
309 offlineLayer->
setDataSource( remoteLayer->source(), remoteLayer->name(), remoteLayer->dataProvider()->name() );
325 const QgsFields fields = remoteLayer->fields();
326 for (
const QgsField &field : fields )
328 if ( !remoteLayer->dataProvider()->defaultValueClause( remoteLayer->fields().fieldOriginIndex( remoteLayer->fields().indexOf( field.name() ) ) ).isEmpty() )
340 const QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
341 sqlExec( database.get(), sql );
345void QgsOfflineEditing::initializeSpatialMetadata(
sqlite3 *sqlite_handle )
347#ifdef HAVE_SPATIALITE
349 if ( !sqlite_handle )
352 char **results =
nullptr;
354 int ret = sqlite3_get_table( sqlite_handle,
"select count(*) from sqlite_master", &results, &rows, &columns,
nullptr );
355 if ( ret != SQLITE_OK )
360 for (
int i = 1; i <= rows; i++ )
361 count = atoi( results[( i * columns ) + 0] );
364 sqlite3_free_table( results );
369 bool above41 =
false;
370 ret = sqlite3_get_table( sqlite_handle,
"select spatialite_version()", &results, &rows, &columns,
nullptr );
371 if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
373 const QString version = QString::fromUtf8( results[1] );
374 const QStringList parts = version.split(
' ', Qt::SkipEmptyParts );
375 if ( !parts.empty() )
377 const QStringList verparts = parts.at( 0 ).split(
'.', Qt::SkipEmptyParts );
378 above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
382 sqlite3_free_table( results );
385 char *errMsg =
nullptr;
386 ret = sqlite3_exec( sqlite_handle, above41 ?
"SELECT InitSpatialMetadata(1)" :
"SELECT InitSpatialMetadata()", nullptr, nullptr, &errMsg );
388 if ( ret != SQLITE_OK )
390 QString errCause = tr(
"Unable to initialize SpatialMetadata:\n" );
391 errCause += QString::fromUtf8( errMsg );
392 showWarning( errCause );
393 sqlite3_free( errMsg );
396 spatial_ref_sys_init( sqlite_handle, 0 );
398 ( void )sqlite_handle;
402bool QgsOfflineEditing::createOfflineDb(
const QString &offlineDbPath, ContainerType containerType )
405 char *errMsg =
nullptr;
406 const QFile newDb( offlineDbPath );
407 if ( newDb.exists() )
409 QFile::remove( offlineDbPath );
414 const QFileInfo fullPath = QFileInfo( offlineDbPath );
415 const QDir path = fullPath.dir();
418 QDir().mkpath( path.absolutePath() );
421 const QString dbPath = newDb.fileName();
424 switch ( containerType )
428 OGRSFDriverH hGpkgDriver = OGRGetDriverByName(
"GPKG" );
431 showWarning( tr(
"Creation of database failed. GeoPackage driver not found." ) );
438 showWarning( tr(
"Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
450 ret = database.
open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
nullptr );
454 QString errCause = tr(
"Could not create a new database\n" );
456 showWarning( errCause );
460 ret = sqlite3_exec( database.get(),
"PRAGMA foreign_keys = 1",
nullptr,
nullptr, &errMsg );
461 if ( ret != SQLITE_OK )
463 showWarning( tr(
"Unable to activate FOREIGN_KEY constraints" ) );
464 sqlite3_free( errMsg );
467 initializeSpatialMetadata( database.get() );
471void QgsOfflineEditing::createLoggingTables(
sqlite3 *db )
474 QString sql = QStringLiteral(
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
477 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
480 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
484 sql = QStringLiteral(
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
488 sql = QStringLiteral(
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER, 'remote_pk' TEXT)" );
492 sql = QStringLiteral(
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
493 sql += QLatin1String(
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
497 sql = QStringLiteral(
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
501 sql = QStringLiteral(
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
505 sql = QStringLiteral(
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
509 sql = QStringLiteral(
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
517void QgsOfflineEditing::convertToOfflineLayer(
QgsVectorLayer *layer,
sqlite3 *db,
const QString &offlineDbPath,
bool onlySelected, ContainerType containerType,
const QString &layerNameSuffix )
519 if ( !layer || !layer->
isValid() )
521 QgsDebugMsgLevel( QStringLiteral(
"Layer %1 is invalid and cannot be copied" ).arg( layer ? layer->
id() : QStringLiteral(
"<UNKNOWN>" ) ), 4 );
525 const QString tableName = layer->
id();
526 QgsDebugMsgLevel( QStringLiteral(
"Creating offline table %1 ..." ).arg( tableName ), 4 );
529 std::unique_ptr<QgsVectorLayer> newLayer;
531 switch ( containerType )
535#ifdef HAVE_SPATIALITE
537 QString sql = QStringLiteral(
"CREATE TABLE '%1' (" ).arg( tableName );
540 for (
const auto &field : providerFields )
543 const QMetaType::Type type = field.type();
544 if ( type == QMetaType::Type::Int || type == QMetaType::Type::LongLong )
546 dataType = QStringLiteral(
"INTEGER" );
548 else if ( type == QMetaType::Type::Double )
550 dataType = QStringLiteral(
"REAL" );
552 else if ( type == QMetaType::Type::QString )
554 dataType = QStringLiteral(
"TEXT" );
556 else if ( type == QMetaType::Type::QStringList || type == QMetaType::Type::QVariantList )
558 dataType = QStringLiteral(
"TEXT" );
559 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() ) );
563 showWarning( tr(
"%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
566 sql += delim + QStringLiteral(
"'%1' %2" ).arg( field.name(), dataType );
571 int rc = sqlExec( db, sql );
582 geomType = QStringLiteral(
"POINT" );
585 geomType = QStringLiteral(
"MULTIPOINT" );
588 geomType = QStringLiteral(
"LINESTRING" );
591 geomType = QStringLiteral(
"MULTILINESTRING" );
594 geomType = QStringLiteral(
"POLYGON" );
597 geomType = QStringLiteral(
"MULTIPOLYGON" );
604 QString zmInfo = QStringLiteral(
"XY" );
613 if ( layer->
crs().
authid().startsWith( QLatin1String(
"EPSG:" ), Qt::CaseInsensitive ) )
615 epsgCode = layer->
crs().
authid().mid( 5 );
620 showWarning( tr(
"Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->
name(), layer->
crs().
authid() ) );
623 const QString sqlAddGeom = QStringLiteral(
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')" )
624 .arg( tableName, epsgCode, geomType, zmInfo );
627 const QString sqlCreateIndex = QStringLiteral(
"SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
629 if ( rc == SQLITE_OK )
631 rc = sqlExec( db, sqlAddGeom );
632 if ( rc == SQLITE_OK )
634 rc = sqlExec( db, sqlCreateIndex );
639 if ( rc != SQLITE_OK )
641 showWarning( tr(
"Filling SpatiaLite for layer %1 failed" ).arg( layer->
name() ) );
646 const QString connectionString = QStringLiteral(
"dbname='%1' table='%2'%3 sql=" )
648 tableName, layer->
isSpatial() ?
"(Geometry)" :
"" );
650 newLayer = std::make_unique<QgsVectorLayer>( connectionString,
651 layer->
name() + layerNameSuffix, QStringLiteral(
"spatialite" ), options );
655 showWarning( tr(
"No Spatialite support available" ) );
663 char **options =
nullptr;
665 options = CSLSetNameValue( options,
"OVERWRITE",
"YES" );
666 options = CSLSetNameValue( options,
"IDENTIFIER", tr(
"%1 (offline)" ).arg( layer->
id() ).toUtf8().constData() );
667 options = CSLSetNameValue( options,
"DESCRIPTION", layer->
dataComment().toUtf8().constData() );
670 const QString fidBase( QStringLiteral(
"fid" ) );
671 QString fid = fidBase;
675 fid = fidBase +
'_' + QString::number( counter );
678 if ( counter == 10000 )
680 showWarning( tr(
"Cannot make FID-name for GPKG " ) );
684 options = CSLSetNameValue( options,
"FID", fid.toUtf8().constData() );
688 options = CSLSetNameValue( options,
"GEOMETRY_COLUMN",
"geom" );
689 options = CSLSetNameValue( options,
"SPATIAL_INDEX",
"YES" );
692 OGRSFDriverH hDriver =
nullptr;
695 OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS,
static_cast<OGRwkbGeometryType
>( layer->
wkbType() ), options );
696 CSLDestroy( options );
701 showWarning( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
706 for (
const auto &field : providerFields )
708 const QString fieldName( field.name() );
709 const QMetaType::Type type = field.type();
710 OGRFieldType ogrType( OFTString );
711 OGRFieldSubType ogrSubType = OFSTNone;
712 if ( type == QMetaType::Type::Int )
713 ogrType = OFTInteger;
714 else if ( type == QMetaType::Type::LongLong )
715 ogrType = OFTInteger64;
716 else if ( type == QMetaType::Type::Double )
718 else if ( type == QMetaType::Type::QTime )
720 else if ( type == QMetaType::Type::QDate )
722 else if ( type == QMetaType::Type::QDateTime )
723 ogrType = OFTDateTime;
724 else if ( type == QMetaType::Type::Bool )
726 ogrType = OFTInteger;
727 ogrSubType = OFSTBoolean;
729 else if ( type == QMetaType::Type::QStringList || type == QMetaType::Type::QVariantList )
732 ogrSubType = OFSTJSON;
733 showWarning( tr(
"Field '%1' from layer %2 has been converted from a list to a JSON-formatted string value." ).arg( fieldName, layer->
name() ) );
738 const int ogrWidth = field.length();
741 OGR_Fld_SetWidth( fld.get(), ogrWidth );
742 if ( ogrSubType != OFSTNone )
743 OGR_Fld_SetSubType( fld.get(), ogrSubType );
745 if ( OGR_L_CreateField( hLayer, fld.get(),
true ) != OGRERR_NONE )
747 showWarning( tr(
"Creation of field %1 failed (OGR error: %2)" )
748 .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
756 OGR_L_ResetReading( hLayer );
757 if ( CPLGetLastErrorType() != CE_None )
759 const QString msg( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
765 const QString uri = QStringLiteral(
"%1|layername=%2|option:QGIS_FORCE_WAL=ON" ).arg( offlineDbPath, tableName );
767 newLayer = std::make_unique<QgsVectorLayer>( uri, layer->
name() + layerNameSuffix, QStringLiteral(
"ogr" ), layerOptions );
772 if ( newLayer && newLayer->isValid() )
776 newLayer->startEditing();
784 if ( !selectedFids.isEmpty() )
798 long long featureCount = 1;
799 const int remotePkIdx = getLayerPkIdx( layer );
801 QList<QgsFeatureId> remoteFeatureIds;
802 QStringList remoteFeaturePks;
805 remoteFeatureIds << f.
id();
806 remoteFeaturePks << ( remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString() );
813 QgsAttributes newAttrs( containerType ==
GPKG ? attrs.count() + 1 : attrs.count() );
814 for (
int it = 0; it < attrs.count(); ++it )
816 const QVariant attr = attrs.at( it );
817 newAttrs[column++] = attr;
821 newLayer->addFeature( f );
825 if ( newLayer->commitChanges() )
831 const int layerId = getOrCreateLayerId( db, layer->
id() );
832 QList<QgsFeatureId> offlineFeatureIds;
837 offlineFeatureIds << f.
id();
841 sqlExec( db, QStringLiteral(
"BEGIN" ) );
842 const int remoteCount = remoteFeatureIds.size();
843 for (
int i = 0; i < remoteCount; i++ )
846 if ( i < offlineFeatureIds.count() )
848 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ), remoteFeaturePks.at( i ) );
852 showWarning( tr(
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
857 sqlExec( db, QStringLiteral(
"COMMIT" ) );
861 showWarning( newLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
875 QStringList notNullFieldNames;
876 for (
const QgsField &field : fields )
880 notNullFieldNames << field.name();
884 layer->
setDataSource( newLayer->source(), newLayer->name(), newLayer->dataProvider()->name() );
886 for (
const QgsField &field : fields )
896 if ( notNullFieldNames.contains( field.name() ) )
898 notNullFieldNames.removeAll( field.name() );
909void QgsOfflineEditing::applyAttributesAdded(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
911 Q_ASSERT( remoteLayer );
913 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 );
914 QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
917 const QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
920 QMap < QMetaType::Type, QString > typeNameLookup;
921 for (
int i = 0; i < nativeTypes.size(); i++ )
929 for (
int i = 0; i < fields.size(); i++ )
933 if ( typeNameLookup.contains( field.
type() ) )
941 showWarning( QStringLiteral(
"Could not add attribute '%1' of type %2" ).arg( field.
name() ).arg( field.
type() ) );
950 Q_ASSERT( offlineLayer );
951 Q_ASSERT( remoteLayer );
953 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
954 const QList<int> featureIdInts = sqlQueryInts( db, sql );
956 for (
const int id : featureIdInts )
976 const int newAttrsCount = remoteLayer->
fields().
count();
977 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
981 const QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
984 for (
int it = 0; it < attrs.count(); ++it )
986 const int remoteAttributeIndex = attrLookup.value( it, -1 );
988 if ( remoteAttributeIndex == -1 )
990 QVariant attr = attrs.at( it );
991 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QStringList )
993 if ( attr.userType() == QMetaType::Type::QStringList || attr.userType() == QMetaType::Type::QVariantList )
995 attr = attr.toStringList();
1002 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QVariantList )
1004 if ( attr.userType() == QMetaType::Type::QStringList || attr.userType() == QMetaType::Type::QVariantList )
1006 attr = attr.toList();
1013 newAttrs[ remoteAttributeIndex ] = attr;
1024void QgsOfflineEditing::applyFeaturesRemoved(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId )
1026 Q_ASSERT( remoteLayer );
1028 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1029 const QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
1034 for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
1036 const QgsFeatureId fid = remoteFid( db, layerId, *it, remoteLayer );
1045 Q_ASSERT( offlineLayer );
1046 Q_ASSERT( remoteLayer );
1048 const QString sql = QStringLiteral(
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
1049 const AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
1053 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1055 for (
int i = 0; i < values.size(); i++ )
1057 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1058 QgsDebugMsgLevel( QStringLiteral(
"Offline changeAttributeValue %1 = %2" ).arg( attrLookup[ values.at( i ).attr ] ).arg( values.at( i ).value ), 4 );
1060 const int remoteAttributeIndex = attrLookup[ values.at( i ).attr ];
1061 QVariant attr = values.at( i ).value;
1062 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QStringList )
1066 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QVariantList )
1077void QgsOfflineEditing::applyGeometryChanges(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
1079 Q_ASSERT( remoteLayer );
1081 const QString sql = QStringLiteral(
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
1082 const GeometryChanges values = sqlQueryGeometryChanges( db, sql );
1086 for (
int i = 0; i < values.size(); i++ )
1088 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1098 Q_ASSERT( remoteLayer );
1104 QMap < QgsFeatureId, QString > newRemoteFids;
1111 const int remotePkIdx = getLayerPkIdx( remoteLayer );
1116 if ( offlineFid( db, layerId, f.
id() ) == -1 )
1118 newRemoteFids[ f.
id()] = remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString();
1126 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1127 const QList<int> newOfflineFids = sqlQueryInts( db, sql );
1129 if ( newRemoteFids.size() != newOfflineFids.size() )
1137 sqlExec( db, QStringLiteral(
"BEGIN" ) );
1138 for ( QMap<QgsFeatureId, QString>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
1140 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key(), it.value() );
1142 sqlExec( db, QStringLiteral(
"COMMIT" ) );
1149 Q_ASSERT( offlineLayer );
1150 Q_ASSERT( remoteLayer );
1154 QMap <
int ,
int > attrLookup;
1157 for (
int i = 0; i < offlineAttrs.size(); i++ )
1166void QgsOfflineEditing::showWarning(
const QString &message )
1168 emit
warning( tr(
"Offline Editing Plugin" ), message );
1175 if ( !dbPath.isEmpty() )
1178 const int rc = database.
open( absoluteDbPath );
1179 if ( rc != SQLITE_OK )
1181 QgsDebugError( QStringLiteral(
"Could not open the SpatiaLite logging database" ) );
1182 showWarning( tr(
"Could not open the SpatiaLite logging database" ) );
1192int QgsOfflineEditing::getOrCreateLayerId(
sqlite3 *db,
const QString &qgisLayerId )
1194 QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
1195 int layerId = sqlQueryInt( db, sql, -1 );
1196 if ( layerId == -1 )
1199 sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
1200 const int newLayerId = sqlQueryInt( db, sql, -1 );
1203 sql = QStringLiteral(
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
1208 sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
1211 layerId = newLayerId;
1217int QgsOfflineEditing::getCommitNo(
sqlite3 *db )
1219 const QString sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
1220 return sqlQueryInt( db, sql, -1 );
1223void QgsOfflineEditing::increaseCommitNo(
sqlite3 *db )
1225 const QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
1231 const QString sql = QStringLiteral(
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3, %4 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid ).arg( sqlEscape( remotePk ) );
1237 const int pkIdx = getLayerPkIdx( remoteLayer );
1241 const QString sql = QStringLiteral(
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1242 return sqlQueryInt( db, sql, -1 );
1245 const QString sql = QStringLiteral(
"SELECT \"remote_pk\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1246 QString defaultValue;
1247 const QString pkValue = sqlQueryStr( db, sql, defaultValue );
1249 if ( pkValue.isNull() )
1254 const QString pkFieldName = remoteLayer->
fields().
at( pkIdx ).
name();
1265 const QString sql = QStringLiteral(
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
1266 return sqlQueryInt( db, sql, -1 );
1271 const QString sql = QStringLiteral(
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
1272 return ( sqlQueryInt( db, sql, 0 ) > 0 );
1275int QgsOfflineEditing::sqlExec(
sqlite3 *db,
const QString &sql )
1277 char *errmsg =
nullptr;
1278 const int rc = sqlite3_exec( db, sql.toUtf8(),
nullptr,
nullptr, &errmsg );
1279 if ( rc != SQLITE_OK )
1281 showWarning( errmsg );
1286QString QgsOfflineEditing::sqlQueryStr(
sqlite3 *db,
const QString &sql, QString &defaultValue )
1288 sqlite3_stmt *stmt =
nullptr;
1289 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1291 showWarning( sqlite3_errmsg( db ) );
1292 return defaultValue;
1295 QString value = defaultValue;
1296 const int ret = sqlite3_step( stmt );
1297 if ( ret == SQLITE_ROW )
1299 value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) );
1301 sqlite3_finalize( stmt );
1306int QgsOfflineEditing::sqlQueryInt(
sqlite3 *db,
const QString &sql,
int defaultValue )
1308 sqlite3_stmt *stmt =
nullptr;
1309 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1311 showWarning( sqlite3_errmsg( db ) );
1312 return defaultValue;
1315 int value = defaultValue;
1316 const int ret = sqlite3_step( stmt );
1317 if ( ret == SQLITE_ROW )
1319 value = sqlite3_column_int( stmt, 0 );
1321 sqlite3_finalize( stmt );
1326QList<int> QgsOfflineEditing::sqlQueryInts(
sqlite3 *db,
const QString &sql )
1330 sqlite3_stmt *stmt =
nullptr;
1331 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1333 showWarning( sqlite3_errmsg( db ) );
1337 int ret = sqlite3_step( stmt );
1338 while ( ret == SQLITE_ROW )
1340 values << sqlite3_column_int( stmt, 0 );
1342 ret = sqlite3_step( stmt );
1344 sqlite3_finalize( stmt );
1349QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded(
sqlite3 *db,
const QString &sql )
1351 QList<QgsField> values;
1353 sqlite3_stmt *stmt =
nullptr;
1354 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1356 showWarning( sqlite3_errmsg( db ) );
1360 int ret = sqlite3_step( stmt );
1361 while ( ret == SQLITE_ROW )
1363 const QgsField field( QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) ),
1364 static_cast< QMetaType::Type
>( sqlite3_column_int( stmt, 1 ) ),
1366 sqlite3_column_int( stmt, 2 ),
1367 sqlite3_column_int( stmt, 3 ),
1368 QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 4 ) ) ) );
1371 ret = sqlite3_step( stmt );
1373 sqlite3_finalize( stmt );
1382 sqlite3_stmt *stmt =
nullptr;
1383 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1385 showWarning( sqlite3_errmsg( db ) );
1389 int ret = sqlite3_step( stmt );
1390 while ( ret == SQLITE_ROW )
1392 values << sqlite3_column_int( stmt, 0 );
1394 ret = sqlite3_step( stmt );
1396 sqlite3_finalize( stmt );
1401QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges(
sqlite3 *db,
const QString &sql )
1403 AttributeValueChanges values;
1405 sqlite3_stmt *stmt =
nullptr;
1406 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1408 showWarning( sqlite3_errmsg( db ) );
1412 int ret = sqlite3_step( stmt );
1413 while ( ret == SQLITE_ROW )
1415 AttributeValueChange change;
1416 change.fid = sqlite3_column_int( stmt, 0 );
1417 change.attr = sqlite3_column_int( stmt, 1 );
1418 change.value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 2 ) ) );
1421 ret = sqlite3_step( stmt );
1423 sqlite3_finalize( stmt );
1428QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges(
sqlite3 *db,
const QString &sql )
1430 GeometryChanges values;
1432 sqlite3_stmt *stmt =
nullptr;
1433 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1435 showWarning( sqlite3_errmsg( db ) );
1439 int ret = sqlite3_step( stmt );
1440 while ( ret == SQLITE_ROW )
1442 GeometryChange change;
1443 change.fid = sqlite3_column_int( stmt, 0 );
1444 change.geom_wkt = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 1 ) ) );
1447 ret = sqlite3_step( stmt );
1449 sqlite3_finalize( stmt );
1454void QgsOfflineEditing::committedAttributesAdded(
const QString &qgisLayerId,
const QList<QgsField> &addedAttributes )
1461 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1462 const int commitNo = getCommitNo( database.get() );
1464 for (
const QgsField &field : addedAttributes )
1466 const QString sql = QStringLiteral(
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1469 .arg( field.
name() )
1470 .arg( field.
type() )
1474 sqlExec( database.get(), sql );
1477 increaseCommitNo( database.get() );
1480void QgsOfflineEditing::committedFeaturesAdded(
const QString &qgisLayerId,
const QgsFeatureList &addedFeatures )
1487 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1491 const QString dataSourceString = layer->
source();
1497 if ( !offlinePath.contains(
".gpkg" ) )
1499 tableName = uri.
table();
1504 const QVariantMap decodedUri = ogrProviderMetaData->
decodeUri( dataSourceString );
1505 tableName = decodedUri.value( QStringLiteral(
"layerName" ) ).toString();
1506 if ( tableName.isEmpty() )
1508 showWarning( tr(
"Could not deduce table name from data source %1." ).arg( dataSourceString ) );
1513 const QString sql = QStringLiteral(
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( tableName ).arg( addedFeatures.size() );
1514 const QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1515 for (
int i = newFeatureIds.size() - 1; i >= 0; i-- )
1517 const QString sql = QStringLiteral(
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1519 .arg( newFeatureIds.at( i ) );
1520 sqlExec( database.get(), sql );
1524void QgsOfflineEditing::committedFeaturesRemoved(
const QString &qgisLayerId,
const QgsFeatureIds &deletedFeatureIds )
1531 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1535 if ( isAddedFeature( database.get(), layerId,
id ) )
1538 const QString sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg(
id );
1539 sqlExec( database.get(), sql );
1543 const QString sql = QStringLiteral(
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1546 sqlExec( database.get(), sql );
1551void QgsOfflineEditing::committedAttributeValuesChanges(
const QString &qgisLayerId,
const QgsChangedAttributesMap &changedAttrsMap )
1558 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1559 const int commitNo = getCommitNo( database.get() );
1561 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1564 if ( isAddedFeature( database.get(), layerId, fid ) )
1570 for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1572 QString value = it.value().userType() == QMetaType::Type::QStringList || it.value().userType() == QMetaType::Type::QVariantList ?
QgsJsonUtils::encodeValue( it.value() ) : it.value().toString();
1573 value.replace( QLatin1String(
"'" ), QLatin1String(
"''" ) );
1574 const QString sql = QStringLiteral(
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1580 sqlExec( database.get(), sql );
1584 increaseCommitNo( database.get() );
1587void QgsOfflineEditing::committedGeometriesChanges(
const QString &qgisLayerId,
const QgsGeometryMap &changedGeometries )
1594 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1595 const int commitNo = getCommitNo( database.get() );
1597 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1600 if ( isAddedFeature( database.get(), layerId, fid ) )
1606 const QString sql = QStringLiteral(
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1610 .arg( geom.
asWkt() );
1611 sqlExec( database.get(), sql );
1616 increaseCommitNo( database.get() );
1619void QgsOfflineEditing::startListenFeatureChanges()
1621 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1630 this, &QgsOfflineEditing::committedAttributesAdded );
1632 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1634 this, &QgsOfflineEditing::committedGeometriesChanges );
1637 this, &QgsOfflineEditing::committedFeaturesAdded );
1639 this, &QgsOfflineEditing::committedFeaturesRemoved );
1642void QgsOfflineEditing::stopListenFeatureChanges()
1644 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1653 this, &QgsOfflineEditing::committedAttributesAdded );
1655 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1657 this, &QgsOfflineEditing::committedGeometriesChanges );
1660 this, &QgsOfflineEditing::committedFeaturesAdded );
1662 this, &QgsOfflineEditing::committedFeaturesRemoved );
1665void QgsOfflineEditing::setupLayer(
QgsMapLayer *layer )
1669 if (
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer ) )
1680int QgsOfflineEditing::getLayerPkIdx(
const QgsVectorLayer *layer )
const
1683 if ( pkAttrs.length() == 1 )
1686 const QMetaType::Type pkType = pkField.
type();
1688 if ( pkType == QMetaType::Type::QString )
1697QString QgsOfflineEditing::sqlEscape( QString value )
const
1699 if ( value.isNull() )
1700 return QStringLiteral(
"NULL" );
1702 value.replace(
"'",
"''" );
1704 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.