void QgsOfflineEditing::updateFidLookup( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId ) { // update fid lookup for added features // get remote added fids // NOTE: use QMap for sorted fids QMap < QgsFeatureId, bool /*dummy*/ > newRemoteFids; QgsFeature f; QgsFeatureIterator fit = remoteLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) ); emit progressModeSet( QgsOfflineEditing::ProcessFeatures, remoteLayer->featureCount() ); int i = 1; while ( fit.nextFeature( f ) ) { if ( offlineFid( db, layerId, f.id() ) == -1 ) { newRemoteFids[ f.id()] = true; } emit progressUpdated( i++ ); } // get local added fids // NOTE: fids are sorted QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId ); QList<int> newOfflineFids = sqlQueryInts( db, sql ); if ( newRemoteFids.size() != newOfflineFids.size() ) { //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) ); } else { // add new fid lookups i = 0; sqlExec( db, "BEGIN" ); for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.begin(); it != newRemoteFids.end(); ++it ) { addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() ); } sqlExec( db, "COMMIT" ); } }
void QgsOfflineEditing::updateFidLookup( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId ) { // update fid lookup for added features // get remote added fids // NOTE: use QMap for sorted fids QMap < int, bool /*dummy*/ > newRemoteFids; QgsFeature f; remoteLayer->select( QgsAttributeList(), QgsRectangle(), false, false ); mProgressDialog->setupProgressBar( tr( "%v / %m features processed" ), remoteLayer->featureCount() ); int i = 1; while ( remoteLayer->nextFeature( f ) ) { if ( offlineFid( db, layerId, f.id() ) == -1 ) { newRemoteFids[ f.id()] = true; } mProgressDialog->setProgressValue( i++ ); } // get local added fids // NOTE: fids are sorted QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId ); QList<int> newOfflineFids = sqlQueryInts( db, sql ); if ( newRemoteFids.size() != newOfflineFids.size() ) { //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) ); } else { // add new fid lookups i = 0; sqlExec( db, "BEGIN" ); for ( QMap<int, bool>::const_iterator it = newRemoteFids.begin(); it != newRemoteFids.end(); ++it ) { addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() ); } sqlExec( db, "COMMIT" ); } }
void QgsOfflineEditing::copyVectorLayer( QgsVectorLayer* layer, sqlite3* db, const QString& offlineDbPath ) { if ( layer == NULL ) { return; } QString tableName = layer->name(); // create table QString sql = QString( "CREATE TABLE '%1' (" ).arg( tableName ); QString delim = ""; const QgsFields& fields = layer->dataProvider()->fields(); for ( int idx = 0; idx < fields.count(); ++idx ) { QString dataType = ""; QVariant::Type type = fields[idx].type(); if ( type == QVariant::Int ) { dataType = "INTEGER"; } else if ( type == QVariant::Double ) { dataType = "REAL"; } else if ( type == QVariant::String ) { dataType = "TEXT"; } else { showWarning( tr( "Unknown data type %1" ).arg( type ) ); } sql += delim + QString( "'%1' %2" ).arg( fields[idx].name() ).arg( dataType ); delim = ","; } sql += ")"; // add geometry column QString geomType = ""; switch ( layer->wkbType() ) { case QGis::WKBPoint: geomType = "POINT"; break; case QGis::WKBMultiPoint: geomType = "MULTIPOINT"; break; case QGis::WKBLineString: geomType = "LINESTRING"; break; case QGis::WKBMultiLineString: geomType = "MULTILINESTRING"; break; case QGis::WKBPolygon: geomType = "POLYGON"; break; case QGis::WKBMultiPolygon: geomType = "MULTIPOLYGON"; break; default: showWarning( tr( "QGIS wkbType %1 not supported" ).arg( layer->wkbType() ) ); break; }; QString sqlAddGeom = QString( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', 2)" ) .arg( tableName ) .arg( layer->crs().authid().startsWith( "EPSG:", Qt::CaseInsensitive ) ? layer->crs().authid().mid( 5 ).toLong() : 0 ) .arg( geomType ); // create spatial index QString sqlCreateIndex = QString( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName ); int rc = sqlExec( db, sql ); if ( rc == SQLITE_OK ) { rc = sqlExec( db, sqlAddGeom ); if ( rc == SQLITE_OK ) { rc = sqlExec( db, sqlCreateIndex ); } } if ( rc == SQLITE_OK ) { // add new layer QgsVectorLayer* newLayer = new QgsVectorLayer( QString( "dbname='%1' table='%2'(Geometry) sql=" ) .arg( offlineDbPath ).arg( tableName ), tableName + " (offline)", "spatialite" ); if ( newLayer->isValid() ) { // mark as offline layer newLayer->setCustomProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, true ); // store original layer source newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, layer->source() ); newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, layer->providerType() ); // copy style bool hasLabels = layer->hasLabelsEnabled(); if ( !hasLabels ) { // NOTE: copy symbology before adding the layer so it is displayed correctly copySymbology( layer, newLayer ); } // register this layer with the central layers registry QgsMapLayerRegistry::instance()->addMapLayers( QList<QgsMapLayer *>() << newLayer ); if ( hasLabels ) { // NOTE: copy symbology of layers with labels enabled after adding to project, as it will crash otherwise (WORKAROUND) copySymbology( layer, newLayer ); } // TODO: layer order // copy features newLayer->startEditing(); QgsFeature f; // NOTE: force feature recount for PostGIS layer, else only visible features are counted, before iterating over all features (WORKAROUND) layer->setSubsetString( "" ); QgsFeatureIterator fit = layer->getFeatures(); emit progressModeSet( QgsOfflineEditing::CopyFeatures, layer->featureCount() ); int featureCount = 1; QList<QgsFeatureId> remoteFeatureIds; while ( fit.nextFeature( f ) ) { remoteFeatureIds << f.id(); // NOTE: Spatialite provider ignores position of geometry column // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND) int column = 0; QgsAttributes attrs = f.attributes(); QgsAttributes newAttrs( attrs.count() ); for ( int it = 0; it < attrs.count(); ++it ) { newAttrs[column++] = attrs[it]; } f.setAttributes( newAttrs ); newLayer->addFeature( f, false ); emit progressUpdated( featureCount++ ); } if ( newLayer->commitChanges() ) { emit progressModeSet( QgsOfflineEditing::ProcessFeatures, layer->featureCount() ); featureCount = 1; // update feature id lookup int layerId = getOrCreateLayerId( db, newLayer->id() ); QList<QgsFeatureId> offlineFeatureIds; QgsFeatureIterator fit = newLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) ); while ( fit.nextFeature( f ) ) { offlineFeatureIds << f.id(); } // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature() sqlExec( db, "BEGIN" ); int remoteCount = remoteFeatureIds.size(); for ( int i = 0; i < remoteCount; i++ ) { addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( remoteCount - ( i + 1 ) ) ); emit progressUpdated( featureCount++ ); } sqlExec( db, "COMMIT" ); } else { showWarning( newLayer->commitErrors().join( "\n" ) ); } // remove remote layer QgsMapLayerRegistry::instance()->removeMapLayers( QStringList() << layer->id() ); } } }