48 #include <QDomDocument>
51 #include <QRegularExpression>
53 #include <ogr_srs_api.h>
60 #ifdef HAVE_SPATIALITE
63 #include <spatialite.h>
67 #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
68 #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
69 #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
70 #define CUSTOM_SHOW_FEATURE_COUNT "showFeatureCount"
71 #define CUSTOM_PROPERTY_ORIGINAL_LAYERID "remoteLayerId"
72 #define CUSTOM_PROPERTY_LAYERNAME_SUFFIX "layerNameSuffix"
73 #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
74 #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
95 if ( layerIds.isEmpty() )
100 const QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
101 if ( createOfflineDb( dbPath, containerType ) )
104 const int rc = database.
open( dbPath );
105 if ( rc != SQLITE_OK )
107 showWarning( tr(
"Could not open the SpatiaLite database" ) );
112 createLoggingTables( database.get() );
117 for (
int i = 0; i < layerIds.count(); i++ )
125 const QString origLayerId = vl->
id();
126 convertToOfflineLayer( vl, database.get(), dbPath, onlySelected, containerType, layerNameSuffix );
134 if ( projectTitle.isEmpty() )
138 projectTitle += QLatin1String(
" (offline)" );
169 QMap<int, std::shared_ptr<QgsVectorLayer>> remoteLayersByOfflineId;
170 QMap<int, QgsVectorLayer *> offlineLayersByOfflineId;
172 for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
174 QgsVectorLayer *offlineLayer( qobject_cast<QgsVectorLayer *>( layer_it.value() ) );
176 if ( !offlineLayer || !offlineLayer->
isValid() )
178 QgsDebugMsgLevel( QStringLiteral(
"Skipping offline layer %1 because it is an invalid layer" ).arg( layer_it.key() ), 4 );
187 QString remoteName = offlineLayer->
name();
189 if ( remoteName.endsWith( remoteNameSuffix ) )
190 remoteName.chop( remoteNameSuffix.size() );
193 std::shared_ptr<QgsVectorLayer> remoteLayer = std::make_shared<QgsVectorLayer>( remoteSource, remoteName, remoteProvider, options );
195 if ( ! remoteLayer->isValid() )
197 QgsDebugMsgLevel( QStringLiteral(
"Skipping offline layer %1 because it failed to recreate its corresponding remote layer" ).arg( offlineLayer->
id() ), 4 );
202 if ( remoteLayer->providerType().contains( QLatin1String(
"WFS" ), Qt::CaseInsensitive ) )
213 const QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( offlineLayer->
id() );
214 const int layerId = sqlQueryInt( database.get(), sql, -1 );
218 QgsDebugMsgLevel( QStringLiteral(
"Skipping offline layer %1 because it failed to determine the offline editing layer id" ).arg( offlineLayer->
id() ), 4 );
222 remoteLayersByOfflineId.insert( layerId, remoteLayer );
223 offlineLayersByOfflineId.insert( layerId, offlineLayer );
226 QgsDebugMsgLevel( QStringLiteral(
"Found %1 offline layers in total" ).arg( offlineLayersByOfflineId.count() ), 4 );
228 QMap<QPair<QString, QString>, std::shared_ptr<QgsTransactionGroup>> transactionGroups;
229 if ( useTransaction )
231 for (
const std::shared_ptr<QgsVectorLayer> &remoteLayer : std::as_const( remoteLayersByOfflineId ) )
233 const QString connectionString = QgsTransaction::connectionString( remoteLayer->source() );
234 const QPair<QString, QString> pair( remoteLayer->providerType(), connectionString );
235 std::shared_ptr<QgsTransactionGroup> transactionGroup = transactionGroups.value( pair );
237 if ( !transactionGroup.get() )
238 transactionGroup = std::make_shared<QgsTransactionGroup>();
240 if ( !transactionGroup->addLayer( remoteLayer.get() ) )
242 QgsDebugMsgLevel( QStringLiteral(
"Failed to add a layer %1 into transaction group, will be modified without transaction" ).arg( remoteLayer->name() ), 4 );
246 transactionGroups.insert( pair, transactionGroup );
249 QgsDebugMsgLevel( QStringLiteral(
"Created %1 transaction groups" ).arg( transactionGroups.count() ), 4 );
252 const QList<int> offlineIds = remoteLayersByOfflineId.keys();
253 for (
int offlineLayerId : offlineIds )
255 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId.value( offlineLayerId );
256 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId.value( offlineLayerId );
259 if ( !remoteLayer->startEditing() && !remoteLayer->isEditable() )
261 QgsDebugMsgLevel( QStringLiteral(
"Failed to turn layer %1 into editing mode" ).arg( remoteLayer->name() ), 4 );
266 const int commitNo = getCommitNo( database.get() );
267 QgsDebugMsgLevel( QStringLiteral(
"Found %1 commits" ).arg( commitNo ), 4 );
269 for (
int i = 0; i < commitNo; i++ )
271 QgsDebugMsgLevel( QStringLiteral(
"Apply commits chronologically from %1" ).arg( offlineLayer->
name() ), 4 );
273 applyAttributesAdded( remoteLayer.get(), database.get(), offlineLayerId, i );
274 applyAttributeValueChanges( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId, i );
275 applyGeometryChanges( remoteLayer.get(), database.get(), offlineLayerId, i );
278 applyFeaturesAdded( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId );
279 applyFeaturesRemoved( remoteLayer.get(), database.get(), offlineLayerId );
283 for (
int offlineLayerId : offlineIds )
285 std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId[offlineLayerId];
286 QgsVectorLayer *offlineLayer = offlineLayersByOfflineId[offlineLayerId];
288 if ( !remoteLayer->isEditable() )
291 if ( remoteLayer->commitChanges() )
294 updateFidLookup( remoteLayer.get(), database.get(), offlineLayerId );
298 sql = QStringLiteral(
"DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
299 sqlExec( database.get(), sql );
300 sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
301 sqlExec( database.get(), sql );
302 sql = QStringLiteral(
"DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
303 sqlExec( database.get(), sql );
304 sql = QStringLiteral(
"DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
305 sqlExec( database.get(), sql );
306 sql = QStringLiteral(
"DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
307 sqlExec( database.get(), sql );
311 showWarning( remoteLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
318 remoteLayer->reload();
319 offlineLayer->
setDataSource( remoteLayer->source(), remoteLayer->name(), remoteLayer->dataProvider()->name() );
335 const QgsFields fields = remoteLayer->fields();
338 if ( !remoteLayer->dataProvider()->defaultValueClause( remoteLayer->fields().fieldOriginIndex( remoteLayer->fields().indexOf(
field.
name() ) ) ).isEmpty() )
350 const QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
351 sqlExec( database.get(), sql );
355 void QgsOfflineEditing::initializeSpatialMetadata(
sqlite3 *sqlite_handle )
357 #ifdef HAVE_SPATIALITE
359 if ( !sqlite_handle )
362 char **results =
nullptr;
364 int ret = sqlite3_get_table( sqlite_handle,
"select count(*) from sqlite_master", &results, &rows, &columns,
nullptr );
365 if ( ret != SQLITE_OK )
370 for (
int i = 1; i <= rows; i++ )
371 count = atoi( results[( i * columns ) + 0] );
374 sqlite3_free_table( results );
379 bool above41 =
false;
380 ret = sqlite3_get_table( sqlite_handle,
"select spatialite_version()", &results, &rows, &columns,
nullptr );
381 if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
383 const QString version = QString::fromUtf8( results[1] );
384 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
385 QStringList parts = version.split(
' ', QString::SkipEmptyParts );
387 const QStringList parts = version.split(
' ', Qt::SkipEmptyParts );
389 if ( !parts.empty() )
391 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
392 QStringList verparts = parts.at( 0 ).split(
'.', QString::SkipEmptyParts );
394 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;
420 bool 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() ) ) );
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() );
489 void QgsOfflineEditing::createLoggingTables(
sqlite3 *db )
492 QString sql = QStringLiteral(
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
495 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
498 sql = QStringLiteral(
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
502 sql = QStringLiteral(
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
506 sql = QStringLiteral(
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER, 'remote_pk' TEXT)" );
510 sql = QStringLiteral(
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
511 sql += QLatin1String(
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
515 sql = QStringLiteral(
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
519 sql = QStringLiteral(
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
523 sql = QStringLiteral(
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
527 sql = QStringLiteral(
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
535 void QgsOfflineEditing::convertToOfflineLayer(
QgsVectorLayer *layer,
sqlite3 *db,
const QString &offlineDbPath,
bool onlySelected, ContainerType containerType,
const QString &layerNameSuffix )
537 if ( !layer || !layer->
isValid() )
539 QgsDebugMsgLevel( QStringLiteral(
"Layer %1 is invalid and cannot be copied" ).arg( layer ? layer->
id() : QStringLiteral(
"<UNKNOWN>" ) ), 4 );
543 const QString tableName = layer->
id();
544 QgsDebugMsgLevel( QStringLiteral(
"Creating offline table %1 ..." ).arg( tableName ), 4 );
547 std::unique_ptr<QgsVectorLayer> newLayer;
549 switch ( containerType )
553 #ifdef HAVE_SPATIALITE
555 QString sql = QStringLiteral(
"CREATE TABLE '%1' (" ).arg( tableName );
558 for (
const auto &
field : providerFields )
561 const QVariant::Type type =
field.
type();
562 if ( type == QVariant::Int || type == QVariant::LongLong )
564 dataType = QStringLiteral(
"INTEGER" );
566 else if ( type == QVariant::Double )
568 dataType = QStringLiteral(
"REAL" );
570 else if ( type == QVariant::String )
572 dataType = QStringLiteral(
"TEXT" );
574 else if ( type == QVariant::StringList || type == QVariant::List )
576 dataType = QStringLiteral(
"TEXT" );
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 + QStringLiteral(
"'%1' %2" ).arg(
field.
name(), dataType );
589 int rc = sqlExec( db, sql );
600 geomType = QStringLiteral(
"POINT" );
603 geomType = QStringLiteral(
"MULTIPOINT" );
606 geomType = QStringLiteral(
"LINESTRING" );
609 geomType = QStringLiteral(
"MULTILINESTRING" );
612 geomType = QStringLiteral(
"POLYGON" );
615 geomType = QStringLiteral(
"MULTIPOLYGON" );
622 QString zmInfo = QStringLiteral(
"XY" );
631 if ( layer->
crs().
authid().startsWith( QLatin1String(
"EPSG:" ), 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 = QStringLiteral(
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')" )
642 .arg( tableName, epsgCode, geomType, zmInfo );
645 const QString sqlCreateIndex = QStringLiteral(
"SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
647 if ( rc == SQLITE_OK )
649 rc = sqlExec( db, sqlAddGeom );
650 if ( rc == SQLITE_OK )
652 rc = sqlExec( db, sqlCreateIndex );
657 if ( rc != SQLITE_OK )
659 showWarning( tr(
"Filling SpatiaLite for layer %1 failed" ).arg( layer->
name() ) );
664 const QString connectionString = QStringLiteral(
"dbname='%1' table='%2'%3 sql=" )
666 tableName, layer->
isSpatial() ?
"(Geometry)" :
"" );
668 newLayer = std::make_unique<QgsVectorLayer>( connectionString,
669 layer->
name() + layerNameSuffix, QStringLiteral(
"spatialite" ), options );
673 showWarning( tr(
"No Spatialite support available" ) );
681 char **options =
nullptr;
683 options = CSLSetNameValue( options,
"OVERWRITE",
"YES" );
684 options = CSLSetNameValue( options,
"IDENTIFIER", tr(
"%1 (offline)" ).arg( layer->
id() ).toUtf8().constData() );
685 options = CSLSetNameValue( options,
"DESCRIPTION", layer->
dataComment().toUtf8().constData() );
688 const QString fidBase( QStringLiteral(
"fid" ) );
689 QString fid = fidBase;
693 fid = fidBase +
'_' + QString::number( counter );
696 if ( counter == 10000 )
698 showWarning( tr(
"Cannot make FID-name for GPKG " ) );
702 options = CSLSetNameValue( options,
"FID", fid.toUtf8().constData() );
706 options = CSLSetNameValue( options,
"GEOMETRY_COLUMN",
"geom" );
707 options = CSLSetNameValue( options,
"SPATIAL_INDEX",
"YES" );
710 OGRSFDriverH hDriver =
nullptr;
713 OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS,
static_cast<OGRwkbGeometryType
>( layer->
wkbType() ), options );
714 CSLDestroy( options );
719 showWarning( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
724 for (
const auto &
field : providerFields )
727 const QVariant::Type type =
field.
type();
728 OGRFieldType ogrType( OFTString );
729 OGRFieldSubType ogrSubType = OFSTNone;
730 if ( type == QVariant::Int )
731 ogrType = OFTInteger;
732 else if ( type == QVariant::LongLong )
733 ogrType = OFTInteger64;
734 else if ( type == QVariant::Double )
736 else if ( type == QVariant::Time )
738 else if ( type == QVariant::Date )
740 else if ( type == QVariant::DateTime )
741 ogrType = OFTDateTime;
742 else if ( type == QVariant::Bool )
744 ogrType = OFTInteger;
745 ogrSubType = OFSTBoolean;
747 else if ( type == QVariant::StringList || type == QVariant::List )
750 ogrSubType = OFSTJSON;
751 showWarning( tr(
"Field '%1' from layer %2 has been converted from a list to a JSON-formatted string value." ).arg( fieldName, layer->
name() ) );
759 OGR_Fld_SetWidth( fld.get(), ogrWidth );
760 if ( ogrSubType != OFSTNone )
761 OGR_Fld_SetSubType( fld.get(), ogrSubType );
763 if ( OGR_L_CreateField( hLayer, fld.get(),
true ) != OGRERR_NONE )
765 showWarning( tr(
"Creation of field %1 failed (OGR error: %2)" )
766 .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
774 OGR_L_ResetReading( hLayer );
775 if ( CPLGetLastErrorType() != CE_None )
777 const QString msg( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
783 const QString uri = QStringLiteral(
"%1|layername=%2" ).arg( offlineDbPath, tableName );
785 newLayer = std::make_unique<QgsVectorLayer>( uri, layer->
name() + layerNameSuffix, QStringLiteral(
"ogr" ), layerOptions );
790 if ( newLayer && newLayer->isValid() )
794 newLayer->startEditing();
802 if ( !selectedFids.isEmpty() )
816 long long featureCount = 1;
817 const int remotePkIdx = getLayerPkIdx( layer );
819 QList<QgsFeatureId> remoteFeatureIds;
820 QStringList remoteFeaturePks;
823 remoteFeatureIds << f.
id();
824 remoteFeaturePks << ( remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString() );
831 QgsAttributes newAttrs( containerType ==
GPKG ? attrs.count() + 1 : attrs.count() );
832 for (
int it = 0; it < attrs.count(); ++it )
834 QVariant attr = attrs.at( it );
839 newAttrs[column++] = attr;
843 newLayer->addFeature( f );
847 if ( newLayer->commitChanges() )
853 const int layerId = getOrCreateLayerId( db, layer->
id() );
854 QList<QgsFeatureId> offlineFeatureIds;
859 offlineFeatureIds << f.
id();
863 sqlExec( db, QStringLiteral(
"BEGIN" ) );
864 const int remoteCount = remoteFeatureIds.size();
865 for (
int i = 0; i < remoteCount; i++ )
868 if ( i < offlineFeatureIds.count() )
870 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ), remoteFeaturePks.at( i ) );
874 showWarning( tr(
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
879 sqlExec( db, QStringLiteral(
"COMMIT" ) );
883 showWarning( newLayer->commitErrors().join( QLatin1Char(
'\n' ) ) );
897 QStringList notNullFieldNames;
906 layer->
setDataSource( newLayer->source(), newLayer->name(), newLayer->dataProvider()->name() );
918 if ( notNullFieldNames.contains(
field.
name() ) )
920 notNullFieldNames.removeAll(
field.
name() );
931 void QgsOfflineEditing::applyAttributesAdded(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
933 Q_ASSERT( remoteLayer );
935 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 );
936 QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
939 const QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
942 QMap < QVariant::Type, QString > typeNameLookup;
943 for (
int i = 0; i < nativeTypes.size(); i++ )
951 for (
int i = 0; i < fields.size(); i++ )
955 if ( typeNameLookup.contains(
field.
type() ) )
963 showWarning( QStringLiteral(
"Could not add attribute '%1' of type %2" ).arg(
field.
name() ).arg(
field.
type() ) );
972 Q_ASSERT( offlineLayer );
973 Q_ASSERT( remoteLayer );
975 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
976 const QList<int> featureIdInts = sqlQueryInts( db, sql );
978 for (
const int id : featureIdInts )
998 const int newAttrsCount = remoteLayer->
fields().
count();
999 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
1003 const QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1006 for (
int it = 0; it < attrs.count(); ++it )
1008 const int remoteAttributeIndex = attrLookup.value( it, -1 );
1010 if ( remoteAttributeIndex == -1 )
1012 QVariant attr = attrs.at( it );
1013 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::StringList )
1015 if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
1017 attr = attr.toStringList();
1024 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::List )
1026 if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
1028 attr = attr.toList();
1035 newAttrs[ remoteAttributeIndex ] = attr;
1046 void QgsOfflineEditing::applyFeaturesRemoved(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId )
1048 Q_ASSERT( remoteLayer );
1050 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1051 const QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
1056 for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
1058 const QgsFeatureId fid = remoteFid( db, layerId, *it, remoteLayer );
1067 Q_ASSERT( offlineLayer );
1068 Q_ASSERT( remoteLayer );
1070 const QString sql = QStringLiteral(
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
1071 const AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
1075 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1077 for (
int i = 0; i < values.size(); i++ )
1079 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1080 QgsDebugMsgLevel( QStringLiteral(
"Offline changeAttributeValue %1 = %2" ).arg( attrLookup[ values.at( i ).attr ] ).arg( values.at( i ).value ), 4 );
1082 const int remoteAttributeIndex = attrLookup[ values.at( i ).attr ];
1083 QVariant attr = values.at( i ).value;
1084 if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::StringList )
1088 else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::List )
1099 void QgsOfflineEditing::applyGeometryChanges(
QgsVectorLayer *remoteLayer,
sqlite3 *db,
int layerId,
int commitNo )
1101 Q_ASSERT( remoteLayer );
1103 const QString sql = QStringLiteral(
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
1104 const GeometryChanges values = sqlQueryGeometryChanges( db, sql );
1108 for (
int i = 0; i < values.size(); i++ )
1110 const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1120 Q_ASSERT( remoteLayer );
1126 QMap < QgsFeatureId, QString > newRemoteFids;
1133 const int remotePkIdx = getLayerPkIdx( remoteLayer );
1138 if ( offlineFid( db, layerId, f.
id() ) == -1 )
1140 newRemoteFids[ f.
id()] = remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString();
1148 const QString sql = QStringLiteral(
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1149 const QList<int> newOfflineFids = sqlQueryInts( db, sql );
1151 if ( newRemoteFids.size() != newOfflineFids.size() )
1159 sqlExec( db, QStringLiteral(
"BEGIN" ) );
1160 for ( QMap<QgsFeatureId, QString>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
1162 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key(), it.value() );
1164 sqlExec( db, QStringLiteral(
"COMMIT" ) );
1171 Q_ASSERT( offlineLayer );
1172 Q_ASSERT( remoteLayer );
1176 QMap <
int ,
int > attrLookup;
1179 for (
int i = 0; i < offlineAttrs.size(); i++ )
1188 void QgsOfflineEditing::showWarning(
const QString &message )
1190 emit
warning( tr(
"Offline Editing Plugin" ), message );
1197 if ( !dbPath.isEmpty() )
1200 const int rc = database.
open( absoluteDbPath );
1201 if ( rc != SQLITE_OK )
1203 QgsDebugMsg( QStringLiteral(
"Could not open the SpatiaLite logging database" ) );
1204 showWarning( tr(
"Could not open the SpatiaLite logging database" ) );
1209 QgsDebugMsg( QStringLiteral(
"dbPath is empty!" ) );
1214 int QgsOfflineEditing::getOrCreateLayerId(
sqlite3 *db,
const QString &qgisLayerId )
1216 QString sql = QStringLiteral(
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
1217 int layerId = sqlQueryInt( db, sql, -1 );
1218 if ( layerId == -1 )
1221 sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
1222 const int newLayerId = sqlQueryInt( db, sql, -1 );
1225 sql = QStringLiteral(
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
1230 sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
1233 layerId = newLayerId;
1239 int QgsOfflineEditing::getCommitNo(
sqlite3 *db )
1241 const QString sql = QStringLiteral(
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
1242 return sqlQueryInt( db, sql, -1 );
1245 void QgsOfflineEditing::increaseCommitNo(
sqlite3 *db )
1247 const QString sql = QStringLiteral(
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
1253 const QString sql = QStringLiteral(
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3, %4 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid ).arg( sqlEscape( remotePk ) );
1259 const int pkIdx = getLayerPkIdx( remoteLayer );
1263 const QString sql = QStringLiteral(
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1264 return sqlQueryInt( db, sql, -1 );
1267 const QString sql = QStringLiteral(
"SELECT \"remote_pk\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1268 QString defaultValue;
1269 const QString pkValue = sqlQueryStr( db, sql, defaultValue );
1271 if ( pkValue.isNull() )
1276 const QString pkFieldName = remoteLayer->
fields().
at( pkIdx ).
name();
1287 const QString sql = QStringLiteral(
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
1288 return sqlQueryInt( db, sql, -1 );
1293 const QString sql = QStringLiteral(
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
1294 return ( sqlQueryInt( db, sql, 0 ) > 0 );
1297 int QgsOfflineEditing::sqlExec(
sqlite3 *db,
const QString &sql )
1299 char *errmsg =
nullptr;
1300 const int rc = sqlite3_exec( db, sql.toUtf8(),
nullptr,
nullptr, &errmsg );
1301 if ( rc != SQLITE_OK )
1303 showWarning( errmsg );
1308 QString QgsOfflineEditing::sqlQueryStr(
sqlite3 *db,
const QString &sql, QString &defaultValue )
1310 sqlite3_stmt *stmt =
nullptr;
1311 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1313 showWarning( sqlite3_errmsg( db ) );
1314 return defaultValue;
1317 QString value = defaultValue;
1318 const int ret = sqlite3_step( stmt );
1319 if ( ret == SQLITE_ROW )
1321 value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) );
1323 sqlite3_finalize( stmt );
1328 int QgsOfflineEditing::sqlQueryInt(
sqlite3 *db,
const QString &sql,
int defaultValue )
1330 sqlite3_stmt *stmt =
nullptr;
1331 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1333 showWarning( sqlite3_errmsg( db ) );
1334 return defaultValue;
1337 int value = defaultValue;
1338 const int ret = sqlite3_step( stmt );
1339 if ( ret == SQLITE_ROW )
1341 value = sqlite3_column_int( stmt, 0 );
1343 sqlite3_finalize( stmt );
1348 QList<int> QgsOfflineEditing::sqlQueryInts(
sqlite3 *db,
const QString &sql )
1352 sqlite3_stmt *stmt =
nullptr;
1353 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1355 showWarning( sqlite3_errmsg( db ) );
1359 int ret = sqlite3_step( stmt );
1360 while ( ret == SQLITE_ROW )
1362 values << sqlite3_column_int( stmt, 0 );
1364 ret = sqlite3_step( stmt );
1366 sqlite3_finalize( stmt );
1371 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded(
sqlite3 *db,
const QString &sql )
1373 QList<QgsField> values;
1375 sqlite3_stmt *stmt =
nullptr;
1376 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1378 showWarning( sqlite3_errmsg( db ) );
1382 int ret = sqlite3_step( stmt );
1383 while ( ret == SQLITE_ROW )
1385 const QgsField field( QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 0 ) ) ),
1386 static_cast< QVariant::Type
>( sqlite3_column_int( stmt, 1 ) ),
1388 sqlite3_column_int( stmt, 2 ),
1389 sqlite3_column_int( stmt, 3 ),
1390 QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 4 ) ) ) );
1393 ret = sqlite3_step( stmt );
1395 sqlite3_finalize( stmt );
1404 sqlite3_stmt *stmt =
nullptr;
1405 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1407 showWarning( sqlite3_errmsg( db ) );
1411 int ret = sqlite3_step( stmt );
1412 while ( ret == SQLITE_ROW )
1414 values << sqlite3_column_int( stmt, 0 );
1416 ret = sqlite3_step( stmt );
1418 sqlite3_finalize( stmt );
1423 QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges(
sqlite3 *db,
const QString &sql )
1425 AttributeValueChanges values;
1427 sqlite3_stmt *stmt =
nullptr;
1428 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1430 showWarning( sqlite3_errmsg( db ) );
1434 int ret = sqlite3_step( stmt );
1435 while ( ret == SQLITE_ROW )
1437 AttributeValueChange change;
1438 change.fid = sqlite3_column_int( stmt, 0 );
1439 change.attr = sqlite3_column_int( stmt, 1 );
1440 change.value = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 2 ) ) );
1443 ret = sqlite3_step( stmt );
1445 sqlite3_finalize( stmt );
1450 QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges(
sqlite3 *db,
const QString &sql )
1452 GeometryChanges values;
1454 sqlite3_stmt *stmt =
nullptr;
1455 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt,
nullptr ) != SQLITE_OK )
1457 showWarning( sqlite3_errmsg( db ) );
1461 int ret = sqlite3_step( stmt );
1462 while ( ret == SQLITE_ROW )
1464 GeometryChange change;
1465 change.fid = sqlite3_column_int( stmt, 0 );
1466 change.geom_wkt = QString(
reinterpret_cast< const char *
>( sqlite3_column_text( stmt, 1 ) ) );
1469 ret = sqlite3_step( stmt );
1471 sqlite3_finalize( stmt );
1476 void QgsOfflineEditing::committedAttributesAdded(
const QString &qgisLayerId,
const QList<QgsField> &addedAttributes )
1483 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1484 const int commitNo = getCommitNo( database.get() );
1488 const QString sql = QStringLiteral(
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1496 sqlExec( database.get(), sql );
1499 increaseCommitNo( database.get() );
1502 void QgsOfflineEditing::committedFeaturesAdded(
const QString &qgisLayerId,
const QgsFeatureList &addedFeatures )
1509 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1513 const QString dataSourceString = layer->
source();
1519 if ( !offlinePath.contains(
".gpkg" ) )
1521 tableName = uri.
table();
1526 const QVariantMap decodedUri = ogrProviderMetaData->
decodeUri( dataSourceString );
1527 tableName = decodedUri.value( QStringLiteral(
"layerName" ) ).toString();
1528 if ( tableName.isEmpty() )
1530 showWarning( tr(
"Could not deduce table name from data source %1." ).arg( dataSourceString ) );
1535 const QString sql = QStringLiteral(
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( tableName ).arg( addedFeatures.size() );
1536 const QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1537 for (
int i = newFeatureIds.size() - 1; i >= 0; i-- )
1539 const QString sql = QStringLiteral(
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1541 .arg( newFeatureIds.at( i ) );
1542 sqlExec( database.get(), sql );
1546 void QgsOfflineEditing::committedFeaturesRemoved(
const QString &qgisLayerId,
const QgsFeatureIds &deletedFeatureIds )
1553 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1557 if ( isAddedFeature( database.get(), layerId,
id ) )
1560 const QString sql = QStringLiteral(
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg(
id );
1561 sqlExec( database.get(), sql );
1565 const QString sql = QStringLiteral(
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1568 sqlExec( database.get(), sql );
1573 void QgsOfflineEditing::committedAttributeValuesChanges(
const QString &qgisLayerId,
const QgsChangedAttributesMap &changedAttrsMap )
1580 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1581 const int commitNo = getCommitNo( database.get() );
1583 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1586 if ( isAddedFeature( database.get(), layerId, fid ) )
1592 for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1594 QString value = it.value().type() == QVariant::StringList || it.value().type() == QVariant::List ?
QgsJsonUtils::encodeValue( it.value() ) : it.value().toString();
1595 value.replace( QLatin1String(
"'" ), QLatin1String(
"''" ) );
1596 const QString sql = QStringLiteral(
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1602 sqlExec( database.get(), sql );
1606 increaseCommitNo( database.get() );
1609 void QgsOfflineEditing::committedGeometriesChanges(
const QString &qgisLayerId,
const QgsGeometryMap &changedGeometries )
1616 const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1617 const int commitNo = getCommitNo( database.get() );
1619 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1622 if ( isAddedFeature( database.get(), layerId, fid ) )
1628 const QString sql = QStringLiteral(
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1632 .arg( geom.
asWkt() );
1633 sqlExec( database.get(), sql );
1638 increaseCommitNo( database.get() );
1641 void QgsOfflineEditing::startListenFeatureChanges()
1643 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1652 this, &QgsOfflineEditing::committedAttributesAdded );
1654 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1656 this, &QgsOfflineEditing::committedGeometriesChanges );
1659 this, &QgsOfflineEditing::committedFeaturesAdded );
1661 this, &QgsOfflineEditing::committedFeaturesRemoved );
1664 void QgsOfflineEditing::stopListenFeatureChanges()
1666 QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1675 this, &QgsOfflineEditing::committedAttributesAdded );
1677 this, &QgsOfflineEditing::committedAttributeValuesChanges );
1679 this, &QgsOfflineEditing::committedGeometriesChanges );
1682 this, &QgsOfflineEditing::committedFeaturesAdded );
1684 this, &QgsOfflineEditing::committedFeaturesRemoved );
1687 void QgsOfflineEditing::setupLayer(
QgsMapLayer *layer )
1691 if (
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer ) )
1702 int QgsOfflineEditing::getLayerPkIdx(
const QgsVectorLayer *layer )
const
1705 if ( pkAttrs.length() == 1 )
1708 const QVariant::Type pkType = pkField.
type();
1710 if ( pkType == QVariant::String )
1719 QString QgsOfflineEditing::sqlEscape( QString value )
const
1721 if ( value.isNull() )
1722 return QStringLiteral(
"NULL" );
1724 value.replace(
"'",
"''" );
1726 return QStringLiteral(
"'%1'" ).arg( value );
QString authid() const
Returns the authority identifier for the CRS.
virtual void invalidateConnections(const QString &connection)
Invalidate connections corresponding to specified name.
Class for storing the component parts of a RDBMS data source URI (e.g.
QString table() const
Returns the table name stored in the URI.
QString database() const
Returns the database name stored in the URI.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
FilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
@ FilterFids
Filter using feature IDs.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
@ ConstraintNotNull
Field may not be null.
@ ConstraintUnique
Field must have a unique value.
Q_GADGET Constraints constraints
Encapsulate a field in an attribute table or data source.
QVariant::Type subType() const
If the field is a collection, gets its element's type.
QgsFieldConstraints constraints
void setTypeName(const QString &typeName)
Set the field type.
Container of fields for a vector layer.
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
int count() const
Returns number of items.
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
int fieldOriginIndex(int fieldIdx) const
Returns the field's origin index (its meaning is specific to each type of origin).
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
A geometry is the spatial representation of a feature.
static QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
QString asWkt(int precision=17) const
Exports the geometry to WKT.
static Q_INVOKABLE QString encodeValue(const QVariant &value)
Encodes a value to a JSON string representation, adding appropriate quotations and escaping where req...
static Q_INVOKABLE QVariantList parseArray(const QString &json, QVariant::Type type=QVariant::Invalid)
Parse a simple array (depth=1)
Base class for all map layer types.
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
QString source() const
Returns the source for the layer.
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
QString providerType() const
Returns the provider type (provider key) for this layer.
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
void editingStarted()
Emitted when editing on this layer has started.
QgsCoordinateReferenceSystem crs
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
Q_INVOKABLE void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
void setDataSource(const QString &dataSource, const QString &baseName, const QString &provider, bool loadDefaultStyleFlag=false)
Updates the data source of the layer.
void progressModeSet(QgsOfflineEditing::ProgressMode mode, long long maximum)
Emitted when the mode for the progress of the current operation is set.
void progressUpdated(long long progress)
Emitted with the progress of the current mode.
void layerProgressUpdated(int layer, int numLayers)
Emitted whenever a new layer is being processed.
bool isOfflineProject() const
Returns true if current project is offline.
bool convertToOfflineProject(const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds, bool onlySelected=false, ContainerType containerType=SpatiaLite, const QString &layerNameSuffix=QStringLiteral(" (offline)"))
Convert current project for offline editing.
void warning(const QString &title, const QString &message)
Emitted when a warning needs to be displayed.
void progressStopped()
Emitted when the processing of all layers has finished.
void synchronize(bool useTransaction=false)
Synchronize to remote layers.
ContainerType
Type of offline database container file.
void progressStarted()
Emitted when the process has started.
static OGRSpatialReferenceH crsToOGRSpatialReference(const QgsCoordinateReferenceSystem &crs)
Returns a OGRSpatialReferenceH corresponding to the specified crs object.
QString title() const
Returns the project's title.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
void layerWasAdded(QgsMapLayer *layer)
Emitted when a layer was added to the registry.
QgsSnappingConfig snappingConfig
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
Reads a string from the specified scope and key.
QgsCoordinateTransformContext transformContext
void setTitle(const QString &title)
Sets the project's title.
bool writeEntry(const QString &scope, const QString &key, bool value)
Write a boolean value to the project file.
QString readPath(const QString &filename) const
Transforms a filename read from the project file to an absolute path.
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
bool removeEntry(const QString &scope, const QString &key)
Remove the given key from the specified scope.
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
This is a container for configuration of the snapping of the project.
This is the base class for vector data providers.
long long featureCount() const override=0
Number of features in the layer.
QList< QgsVectorDataProvider::NativeType > nativeTypes() const
Returns the names of the supported types.
virtual QString defaultValueClause(int fieldIndex) const
Returns any default value clauses which are present at the provider for a specified field index.
QgsFields fields() const override=0
Returns the fields associated with this data provider.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override=0
Query the provider for features specified in request.
void committedAttributeValuesChanges(const QString &layerId, const QgsChangedAttributesMap &changedAttributesValues)
void committedAttributesAdded(const QString &layerId, const QList< QgsField > &addedAttributes)
void committedGeometriesChanges(const QString &layerId, const QgsGeometryMap &changedGeometries)
static QgsFeature createFeature(const QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
void committedFeaturesAdded(const QString &layerId, const QgsFeatureList &addedFeatures)
Emitted when features are added to the provider.
bool addAttribute(const QgsField &field)
Add an attribute field (but does not commit it) returns true if the field was added.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
void setFieldConstraint(int index, QgsFieldConstraints::Constraint constraint, QgsFieldConstraints::ConstraintStrength strength=QgsFieldConstraints::ConstraintStrengthHard)
Sets a constraint for a specified field index.
bool deleteFeature(QgsFeatureId fid, DeleteContext *context=nullptr)
Deletes a feature from the layer (but does not commit it).
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsAttributeList attributeList() const
Returns list of attribute indexes.
void removeFieldConstraint(int index, QgsFieldConstraints::Constraint constraint)
Removes a constraint for a specified field index.
void committedFeaturesRemoved(const QString &layerId, const QgsFeatureIds &deletedFeatureIds)
Emitted when features are deleted from the provider.
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.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false)
Changes an attribute value for a feature (but does not immediately commit the changes).
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a single feature to the sink.
QgsAttributeList primaryKeyAttributes() const
Returns the list of attributes which make up the layer's primary keys.
Q_INVOKABLE QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
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 bool hasM(Type type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
Type
The WKB type describes the number of dimensions a geometry has.
static QString displayString(Type type) SIP_HOLDGIL
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
static bool hasZ(Type type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
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 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.