|
QGIS API Documentation
master-3f58142
|
00001 /*************************************************************************** 00002 offline_editing.cpp 00003 00004 Offline Editing Plugin 00005 a QGIS plugin 00006 -------------------------------------- 00007 Date : 22-Jul-2010 00008 Copyright : (C) 2010 by Sourcepole 00009 Email : info at sourcepole.ch 00010 *************************************************************************** 00011 * * 00012 * This program is free software; you can redistribute it and/or modify * 00013 * it under the terms of the GNU General Public License as published by * 00014 * the Free Software Foundation; either version 2 of the License, or * 00015 * (at your option) any later version. * 00016 * * 00017 ***************************************************************************/ 00018 00019 00020 #include <qgsapplication.h> 00021 #include <qgsdatasourceuri.h> 00022 #include <qgsgeometry.h> 00023 #include <qgsmaplayer.h> 00024 #include <qgsmaplayerregistry.h> 00025 #include <qgsofflineediting.h> 00026 #include <qgsproject.h> 00027 #include <qgsvectordataprovider.h> 00028 00029 #include <QDir> 00030 #include <QDomDocument> 00031 #include <QDomNode> 00032 #include <QFile> 00033 #include <QMessageBox> 00034 00035 extern "C" 00036 { 00037 #include <sqlite3.h> 00038 #include <spatialite.h> 00039 } 00040 00041 // TODO: DEBUG 00042 #include <QDebug> 00043 // END 00044 00045 #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable" 00046 #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource" 00047 #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider" 00048 #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin" 00049 #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath" 00050 00051 QgsOfflineEditing::QgsOfflineEditing() 00052 { 00053 connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWasAdded( QgsMapLayer* ) ), this, SLOT( layerAdded( QgsMapLayer* ) ) ); 00054 } 00055 00056 QgsOfflineEditing::~QgsOfflineEditing() 00057 { 00058 } 00059 00064 bool QgsOfflineEditing::convertToOfflineProject( const QString& offlineDataPath, const QString& offlineDbFile, const QStringList& layerIds ) 00065 { 00066 if ( layerIds.isEmpty() ) 00067 { 00068 return false; 00069 } 00070 QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile ); 00071 if ( createSpatialiteDB( dbPath ) ) 00072 { 00073 spatialite_init( 0 ); 00074 sqlite3* db; 00075 int rc = sqlite3_open( dbPath.toStdString().c_str(), &db ); 00076 if ( rc != SQLITE_OK ) 00077 { 00078 showWarning( tr( "Could not open the spatialite database" ) ); 00079 } 00080 else 00081 { 00082 // create logging tables 00083 createLoggingTables( db ); 00084 00085 emit progressStarted(); 00086 00087 // copy selected vector layers to SpatiaLite 00088 for ( int i = 0; i < layerIds.count(); i++ ) 00089 { 00090 emit layerProgressUpdated( i + 1, layerIds.count() ); 00091 00092 QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( layerIds.at( i ) ); 00093 copyVectorLayer( qobject_cast<QgsVectorLayer*>( layer ), db, dbPath ); 00094 } 00095 00096 emit progressStopped(); 00097 00098 sqlite3_close( db ); 00099 00100 // save offline project 00101 QString projectTitle = QgsProject::instance()->title(); 00102 if ( projectTitle.isEmpty() ) 00103 { 00104 projectTitle = QFileInfo( QgsProject::instance()->fileName() ).fileName(); 00105 } 00106 projectTitle += " (offline)"; 00107 QgsProject::instance()->title( projectTitle ); 00108 00109 QgsProject::instance()->writeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH, dbPath ); 00110 00111 return true; 00112 } 00113 } 00114 00115 return false; 00116 00117 // Workflow: 00118 // copy layers to spatialite 00119 // create spatialite db at offlineDataPath 00120 // create table for each layer 00121 // add new spatialite layer 00122 // copy features 00123 // save as offline project 00124 // mark offline layers 00125 // remove remote layers 00126 // mark as offline project 00127 } 00128 00129 bool QgsOfflineEditing::isOfflineProject() 00130 { 00131 return !QgsProject::instance()->readEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH ).isEmpty(); 00132 } 00133 00134 void QgsOfflineEditing::synchronize() 00135 { 00136 // open logging db 00137 sqlite3* db = openLoggingDb(); 00138 if ( db == NULL ) 00139 { 00140 return; 00141 } 00142 00143 emit progressStarted(); 00144 00145 // restore and sync remote layers 00146 QList<QgsMapLayer*> offlineLayers; 00147 QMap<QString, QgsMapLayer*> mapLayers = QgsMapLayerRegistry::instance()->mapLayers(); 00148 for ( QMap<QString, QgsMapLayer*>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it ) 00149 { 00150 QgsMapLayer* layer = layer_it.value(); 00151 if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() ) 00152 { 00153 offlineLayers << layer; 00154 } 00155 } 00156 00157 for ( int l = 0; l < offlineLayers.count(); l++ ) 00158 { 00159 QgsMapLayer* layer = offlineLayers[l]; 00160 00161 emit layerProgressUpdated( l + 1, offlineLayers.count() ); 00162 00163 QString remoteSource = layer->customProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, "" ).toString(); 00164 QString remoteProvider = layer->customProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, "" ).toString(); 00165 QString remoteName = layer->name(); 00166 remoteName.remove( QRegExp( " \\(offline\\)$" ) ); 00167 00168 QgsVectorLayer* remoteLayer = new QgsVectorLayer( remoteSource, remoteName, remoteProvider ); 00169 if ( remoteLayer->isValid() ) 00170 { 00171 // TODO: only add remote layer if there are log entries? 00172 00173 QgsVectorLayer* offlineLayer = qobject_cast<QgsVectorLayer*>( layer ); 00174 00175 // copy style 00176 copySymbology( offlineLayer, remoteLayer ); 00177 00178 // register this layer with the central layers registry 00179 QgsMapLayerRegistry::instance()->addMapLayers( 00180 QList<QgsMapLayer *>() << remoteLayer, true ); 00181 00182 // apply layer edit log 00183 QString qgisLayerId = layer->id(); 00184 QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId ); 00185 int layerId = sqlQueryInt( db, sql, -1 ); 00186 if ( layerId != -1 ) 00187 { 00188 remoteLayer->startEditing(); 00189 00190 // TODO: only get commitNos of this layer? 00191 int commitNo = getCommitNo( db ); 00192 for ( int i = 0; i < commitNo; i++ ) 00193 { 00194 // apply commits chronologically 00195 applyAttributesAdded( remoteLayer, db, layerId, i ); 00196 applyAttributeValueChanges( offlineLayer, remoteLayer, db, layerId, i ); 00197 applyGeometryChanges( remoteLayer, db, layerId, i ); 00198 } 00199 00200 applyFeaturesAdded( offlineLayer, remoteLayer, db, layerId ); 00201 applyFeaturesRemoved( remoteLayer, db, layerId ); 00202 00203 if ( remoteLayer->commitChanges() ) 00204 { 00205 // update fid lookup 00206 updateFidLookup( remoteLayer, db, layerId ); 00207 00208 // clear edit log for this layer 00209 sql = QString( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId ); 00210 sqlExec( db, sql ); 00211 sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId ); 00212 sqlExec( db, sql ); 00213 sql = QString( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId ); 00214 sqlExec( db, sql ); 00215 sql = QString( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId ); 00216 sqlExec( db, sql ); 00217 sql = QString( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId ); 00218 sqlExec( db, sql ); 00219 00220 // reset commitNo 00221 QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" ); 00222 sqlExec( db, sql ); 00223 } 00224 else 00225 { 00226 showWarning( remoteLayer->commitErrors().join( "\n" ) ); 00227 } 00228 } 00229 00230 // remove offline layer 00231 QgsMapLayerRegistry::instance()->removeMapLayers( 00232 ( QStringList() << qgisLayerId ) ); 00233 00234 // disable offline project 00235 QString projectTitle = QgsProject::instance()->title(); 00236 projectTitle.remove( QRegExp( " \\(offline\\)$" ) ); 00237 QgsProject::instance()->title( projectTitle ); 00238 QgsProject::instance()->removeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH ); 00239 remoteLayer->reload(); //update with other changes 00240 } 00241 } 00242 00243 emit progressStopped(); 00244 00245 sqlite3_close( db ); 00246 } 00247 00248 void QgsOfflineEditing::initializeSpatialMetadata( sqlite3 *sqlite_handle ) 00249 { 00250 // attempting to perform self-initialization for a newly created DB 00251 int ret; 00252 char sql[1024]; 00253 char *errMsg = NULL; 00254 int count = 0; 00255 int i; 00256 char **results; 00257 int rows; 00258 int columns; 00259 00260 if ( sqlite_handle == NULL ) 00261 return; 00262 // checking if this DB is really empty 00263 strcpy( sql, "SELECT Count(*) from sqlite_master" ); 00264 ret = sqlite3_get_table( sqlite_handle, sql, &results, &rows, &columns, NULL ); 00265 if ( ret != SQLITE_OK ) 00266 return; 00267 if ( rows < 1 ) 00268 ; 00269 else 00270 { 00271 for ( i = 1; i <= rows; i++ ) 00272 count = atoi( results[( i * columns ) + 0] ); 00273 } 00274 sqlite3_free_table( results ); 00275 00276 if ( count > 0 ) 00277 return; 00278 00279 // all right, it's empty: proceding to initialize 00280 strcpy( sql, "SELECT InitSpatialMetadata()" ); 00281 ret = sqlite3_exec( sqlite_handle, sql, NULL, NULL, &errMsg ); 00282 if ( ret != SQLITE_OK ) 00283 { 00284 QString errCause = tr( "Unable to initialize SpatialMetadata:\n" ); 00285 errCause += QString::fromUtf8( errMsg ); 00286 showWarning( errCause ); 00287 sqlite3_free( errMsg ); 00288 return; 00289 } 00290 spatial_ref_sys_init( sqlite_handle, 0 ); 00291 } 00292 00293 bool QgsOfflineEditing::createSpatialiteDB( const QString& offlineDbPath ) 00294 { 00295 int ret; 00296 sqlite3 *sqlite_handle; 00297 char *errMsg = NULL; 00298 QFile newDb( offlineDbPath ); 00299 if ( newDb.exists() ) 00300 { 00301 QFile::remove( offlineDbPath ); 00302 } 00303 00304 // see also QgsNewSpatialiteLayerDialog::createDb() 00305 00306 QFileInfo fullPath = QFileInfo( offlineDbPath ); 00307 QDir path = fullPath.dir(); 00308 00309 // Must be sure there is destination directory ~/.qgis 00310 QDir().mkpath( path.absolutePath( ) ); 00311 00312 // creating/opening the new database 00313 QString dbPath = newDb.fileName(); 00314 spatialite_init( 0 ); 00315 ret = sqlite3_open_v2( dbPath.toUtf8().constData(), &sqlite_handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL ); 00316 if ( ret ) 00317 { 00318 // an error occurred 00319 QString errCause = tr( "Could not create a new database\n" ); 00320 errCause += QString::fromUtf8( sqlite3_errmsg( sqlite_handle ) ); 00321 sqlite3_close( sqlite_handle ); 00322 showWarning( errCause ); 00323 return false; 00324 } 00325 // activating Foreign Key constraints 00326 ret = sqlite3_exec( sqlite_handle, "PRAGMA foreign_keys = 1", NULL, 0, &errMsg ); 00327 if ( ret != SQLITE_OK ) 00328 { 00329 showWarning( tr( "Unable to activate FOREIGN_KEY constraints" ) ); 00330 sqlite3_free( errMsg ); 00331 sqlite3_close( sqlite_handle ); 00332 return false; 00333 } 00334 initializeSpatialMetadata( sqlite_handle ); 00335 00336 // all done: closing the DB connection 00337 sqlite3_close( sqlite_handle ); 00338 00339 return true; 00340 } 00341 00342 void QgsOfflineEditing::createLoggingTables( sqlite3* db ) 00343 { 00344 // indices 00345 QString sql = "CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)"; 00346 sqlExec( db, sql ); 00347 00348 sql = "INSERT INTO 'log_indices' VALUES ('commit_no', 0)"; 00349 sqlExec( db, sql ); 00350 00351 sql = "INSERT INTO 'log_indices' VALUES ('layer_id', 0)"; 00352 sqlExec( db, sql ); 00353 00354 // layername <-> layer id 00355 sql = "CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)"; 00356 sqlExec( db, sql ); 00357 00358 // offline fid <-> remote fid 00359 sql = "CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)"; 00360 sqlExec( db, sql ); 00361 00362 // added attributes 00363 sql = "CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, "; 00364 sql += "'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)"; 00365 sqlExec( db, sql ); 00366 00367 // added features 00368 sql = "CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)"; 00369 sqlExec( db, sql ); 00370 00371 // removed features 00372 sql = "CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)"; 00373 sqlExec( db, sql ); 00374 00375 // feature updates 00376 sql = "CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)"; 00377 sqlExec( db, sql ); 00378 00379 // geometry updates 00380 sql = "CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)"; 00381 sqlExec( db, sql ); 00382 00383 /* TODO: other logging tables 00384 - attr delete (not supported by SpatiaLite provider) 00385 */ 00386 } 00387 00388 void QgsOfflineEditing::copyVectorLayer( QgsVectorLayer* layer, sqlite3* db, const QString& offlineDbPath ) 00389 { 00390 if ( layer == NULL ) 00391 { 00392 return; 00393 } 00394 00395 QString tableName = layer->name(); 00396 00397 // create table 00398 QString sql = QString( "CREATE TABLE '%1' (" ).arg( tableName ); 00399 QString delim = ""; 00400 const QgsFields& fields = layer->dataProvider()->fields(); 00401 for ( int idx = 0; idx < fields.count(); ++idx ) 00402 { 00403 QString dataType = ""; 00404 QVariant::Type type = fields[idx].type(); 00405 if ( type == QVariant::Int ) 00406 { 00407 dataType = "INTEGER"; 00408 } 00409 else if ( type == QVariant::Double ) 00410 { 00411 dataType = "REAL"; 00412 } 00413 else if ( type == QVariant::String ) 00414 { 00415 dataType = "TEXT"; 00416 } 00417 else 00418 { 00419 showWarning( tr( "Unknown data type %1" ).arg( type ) ); 00420 } 00421 00422 sql += delim + QString( "'%1' %2" ).arg( fields[idx].name() ).arg( dataType ); 00423 delim = ","; 00424 } 00425 sql += ")"; 00426 00427 // add geometry column 00428 QString geomType = ""; 00429 switch ( layer->wkbType() ) 00430 { 00431 case QGis::WKBPoint: 00432 geomType = "POINT"; 00433 break; 00434 case QGis::WKBMultiPoint: 00435 geomType = "MULTIPOINT"; 00436 break; 00437 case QGis::WKBLineString: 00438 geomType = "LINESTRING"; 00439 break; 00440 case QGis::WKBMultiLineString: 00441 geomType = "MULTILINESTRING"; 00442 break; 00443 case QGis::WKBPolygon: 00444 geomType = "POLYGON"; 00445 break; 00446 case QGis::WKBMultiPolygon: 00447 geomType = "MULTIPOLYGON"; 00448 break; 00449 default: 00450 showWarning( tr( "QGIS wkbType %1 not supported" ).arg( layer->wkbType() ) ); 00451 break; 00452 }; 00453 QString sqlAddGeom = QString( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', 2)" ) 00454 .arg( tableName ) 00455 .arg( layer->crs().authid().startsWith( "EPSG:", Qt::CaseInsensitive ) ? layer->crs().authid().mid( 5 ).toLong() : 0 ) 00456 .arg( geomType ); 00457 00458 // create spatial index 00459 QString sqlCreateIndex = QString( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName ); 00460 00461 int rc = sqlExec( db, sql ); 00462 if ( rc == SQLITE_OK ) 00463 { 00464 rc = sqlExec( db, sqlAddGeom ); 00465 if ( rc == SQLITE_OK ) 00466 { 00467 rc = sqlExec( db, sqlCreateIndex ); 00468 } 00469 } 00470 00471 if ( rc == SQLITE_OK ) 00472 { 00473 // add new layer 00474 QgsVectorLayer* newLayer = new QgsVectorLayer( QString( "dbname='%1' table='%2'(Geometry) sql=" ) 00475 .arg( offlineDbPath ).arg( tableName ), tableName + " (offline)", "spatialite" ); 00476 if ( newLayer->isValid() ) 00477 { 00478 // mark as offline layer 00479 newLayer->setCustomProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, true ); 00480 00481 // store original layer source 00482 newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, layer->source() ); 00483 newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, layer->providerType() ); 00484 00485 // copy style 00486 bool hasLabels = layer->hasLabelsEnabled(); 00487 if ( !hasLabels ) 00488 { 00489 // NOTE: copy symbology before adding the layer so it is displayed correctly 00490 copySymbology( layer, newLayer ); 00491 } 00492 00493 // register this layer with the central layers registry 00494 QgsMapLayerRegistry::instance()->addMapLayers( 00495 QList<QgsMapLayer *>() << newLayer ); 00496 00497 if ( hasLabels ) 00498 { 00499 // NOTE: copy symbology of layers with labels enabled after adding to project, as it will crash otherwise (WORKAROUND) 00500 copySymbology( layer, newLayer ); 00501 } 00502 00503 // TODO: layer order 00504 00505 // copy features 00506 newLayer->startEditing(); 00507 QgsFeature f; 00508 00509 // NOTE: force feature recount for PostGIS layer, else only visible features are counted, before iterating over all features (WORKAROUND) 00510 layer->setSubsetString( "" ); 00511 00512 QgsFeatureIterator fit = layer->getFeatures(); 00513 00514 emit progressModeSet( QgsOfflineEditing::CopyFeatures, layer->featureCount() ); 00515 int featureCount = 1; 00516 00517 QList<QgsFeatureId> remoteFeatureIds; 00518 while ( fit.nextFeature( f ) ) 00519 { 00520 remoteFeatureIds << f.id(); 00521 00522 // NOTE: Spatialite provider ignores position of geometry column 00523 // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND) 00524 int column = 0; 00525 QgsAttributes newAttrs; 00526 QgsAttributes attrs = f.attributes(); 00527 for ( int it = 0; it < attrs.count(); ++it ) 00528 { 00529 newAttrs[column++] = attrs[it]; 00530 } 00531 f.setAttributes( newAttrs ); 00532 00533 newLayer->addFeature( f, false ); 00534 00535 emit progressUpdated( featureCount++ ); 00536 } 00537 if ( newLayer->commitChanges() ) 00538 { 00539 emit progressModeSet( QgsOfflineEditing::ProcessFeatures, layer->featureCount() ); 00540 featureCount = 1; 00541 00542 // update feature id lookup 00543 int layerId = getOrCreateLayerId( db, newLayer->id() ); 00544 QList<QgsFeatureId> offlineFeatureIds; 00545 00546 QgsFeatureIterator fit = newLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) ); 00547 while ( fit.nextFeature( f ) ) 00548 { 00549 offlineFeatureIds << f.id(); 00550 } 00551 00552 // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature() 00553 sqlExec( db, "BEGIN" ); 00554 for ( int i = 0; i < remoteFeatureIds.size(); i++ ) 00555 { 00556 addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ) ); 00557 00558 emit progressUpdated( featureCount++ ); 00559 } 00560 sqlExec( db, "COMMIT" ); 00561 } 00562 else 00563 { 00564 showWarning( newLayer->commitErrors().join( "\n" ) ); 00565 } 00566 00567 // remove remote layer 00568 QgsMapLayerRegistry::instance()->removeMapLayers( 00569 QStringList() << layer->id() ); 00570 } 00571 } 00572 } 00573 00574 void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo ) 00575 { 00576 QString sql = QString( "SELECT \"name\", \"type\", \"length\", \"precision\", \"comment\" FROM 'log_added_attrs' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo ); 00577 QList<QgsField> fields = sqlQueryAttributesAdded( db, sql ); 00578 00579 const QgsVectorDataProvider* provider = remoteLayer->dataProvider(); 00580 QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->nativeTypes(); 00581 00582 // NOTE: uses last matching QVariant::Type of nativeTypes 00583 QMap < QVariant::Type, QString /*typeName*/ > typeNameLookup; 00584 for ( int i = 0; i < nativeTypes.size(); i++ ) 00585 { 00586 QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i ); 00587 typeNameLookup[ nativeType.mType ] = nativeType.mTypeName; 00588 } 00589 00590 emit progressModeSet( QgsOfflineEditing::AddFields, fields.size() ); 00591 00592 for ( int i = 0; i < fields.size(); i++ ) 00593 { 00594 // lookup typename from layer provider 00595 QgsField field = fields[i]; 00596 if ( typeNameLookup.contains( field.type() ) ) 00597 { 00598 QString typeName = typeNameLookup[ field.type()]; 00599 field.setTypeName( typeName ); 00600 remoteLayer->addAttribute( field ); 00601 } 00602 else 00603 { 00604 showWarning( QString( "Could not add attribute '%1' of type %2" ).arg( field.name() ).arg( field.type() ) ); 00605 } 00606 00607 emit progressUpdated( i + 1 ); 00608 } 00609 } 00610 00611 void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId ) 00612 { 00613 QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId ); 00614 QList<int> newFeatureIds = sqlQueryInts( db, sql ); 00615 00616 // get new features from offline layer 00617 QgsFeatureList features; 00618 for ( int i = 0; i < newFeatureIds.size(); i++ ) 00619 { 00620 QgsFeature feature; 00621 if ( offlineLayer->getFeatures( QgsFeatureRequest().setFilterFid( newFeatureIds.at( i ) ) ).nextFeature( feature ) ) 00622 { 00623 features << feature; 00624 } 00625 } 00626 00627 // copy features to remote layer 00628 emit progressModeSet( QgsOfflineEditing::AddFeatures, features.size() ); 00629 00630 int i = 1; 00631 int newAttrsCount = remoteLayer->pendingFields().count(); 00632 for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it ) 00633 { 00634 QgsFeature f = *it; 00635 00636 // NOTE: Spatialite provider ignores position of geometry column 00637 // restore gap in QgsAttributeMap if geometry column is not last (WORKAROUND) 00638 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer ); 00639 QgsAttributes newAttrs( newAttrsCount ); 00640 QgsAttributes attrs = f.attributes(); 00641 for ( int it = 0; it < attrs.count(); ++it ) 00642 { 00643 newAttrs[ attrLookup[ it ] ] = attrs[ it ]; 00644 } 00645 f.setAttributes( newAttrs ); 00646 00647 remoteLayer->addFeature( f, false ); 00648 00649 emit progressUpdated( i++ ); 00650 } 00651 } 00652 00653 void QgsOfflineEditing::applyFeaturesRemoved( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId ) 00654 { 00655 QString sql = QString( "SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId ); 00656 QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql ); 00657 00658 emit progressModeSet( QgsOfflineEditing::RemoveFeatures, values.size() ); 00659 00660 int i = 1; 00661 for ( QgsFeatureIds::const_iterator it = values.begin(); it != values.end(); ++it ) 00662 { 00663 QgsFeatureId fid = remoteFid( db, layerId, *it ); 00664 remoteLayer->deleteFeature( fid ); 00665 00666 emit progressUpdated( i++ ); 00667 } 00668 } 00669 00670 void QgsOfflineEditing::applyAttributeValueChanges( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo ) 00671 { 00672 QString sql = QString( "SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo ); 00673 AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql ); 00674 00675 emit progressModeSet( QgsOfflineEditing::UpdateFeatures, values.size() ); 00676 00677 QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer ); 00678 00679 for ( int i = 0; i < values.size(); i++ ) 00680 { 00681 QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid ); 00682 00683 remoteLayer->changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value, false ); 00684 00685 emit progressUpdated( i + 1 ); 00686 } 00687 } 00688 00689 void QgsOfflineEditing::applyGeometryChanges( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo ) 00690 { 00691 QString sql = QString( "SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo ); 00692 GeometryChanges values = sqlQueryGeometryChanges( db, sql ); 00693 00694 emit progressModeSet( QgsOfflineEditing::UpdateGeometries, values.size() ); 00695 00696 for ( int i = 0; i < values.size(); i++ ) 00697 { 00698 QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid ); 00699 remoteLayer->changeGeometry( fid, QgsGeometry::fromWkt( values.at( i ).geom_wkt ) ); 00700 00701 emit progressUpdated( i + 1 ); 00702 } 00703 } 00704 00705 void QgsOfflineEditing::updateFidLookup( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId ) 00706 { 00707 // update fid lookup for added features 00708 00709 // get remote added fids 00710 // NOTE: use QMap for sorted fids 00711 QMap < QgsFeatureId, bool /*dummy*/ > newRemoteFids; 00712 QgsFeature f; 00713 00714 QgsFeatureIterator fit = remoteLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) ); 00715 00716 emit progressModeSet( QgsOfflineEditing::ProcessFeatures, remoteLayer->featureCount() ); 00717 00718 int i = 1; 00719 while ( fit.nextFeature( f ) ) 00720 { 00721 if ( offlineFid( db, layerId, f.id() ) == -1 ) 00722 { 00723 newRemoteFids[ f.id()] = true; 00724 } 00725 00726 emit progressUpdated( i++ ); 00727 } 00728 00729 // get local added fids 00730 // NOTE: fids are sorted 00731 QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId ); 00732 QList<int> newOfflineFids = sqlQueryInts( db, sql ); 00733 00734 if ( newRemoteFids.size() != newOfflineFids.size() ) 00735 { 00736 //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) ); 00737 } 00738 else 00739 { 00740 // add new fid lookups 00741 i = 0; 00742 sqlExec( db, "BEGIN" ); 00743 for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.begin(); it != newRemoteFids.end(); ++it ) 00744 { 00745 addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() ); 00746 } 00747 sqlExec( db, "COMMIT" ); 00748 } 00749 } 00750 00751 void QgsOfflineEditing::copySymbology( const QgsVectorLayer* sourceLayer, QgsVectorLayer* targetLayer ) 00752 { 00753 QString error; 00754 QDomDocument doc; 00755 QDomElement node = doc.createElement( "symbology" ); 00756 doc.appendChild( node ); 00757 sourceLayer->writeSymbology( node, doc, error ); 00758 00759 if ( error.isEmpty() ) 00760 { 00761 targetLayer->readSymbology( node, error ); 00762 } 00763 if ( !error.isEmpty() ) 00764 { 00765 showWarning( error ); 00766 } 00767 } 00768 00769 // NOTE: use this to map column indices in case the remote geometry column is not last 00770 QMap<int, int> QgsOfflineEditing::attributeLookup( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer ) 00771 { 00772 const QgsAttributeList& offlineAttrs = offlineLayer->pendingAllAttributesList(); 00773 const QgsAttributeList& remoteAttrs = remoteLayer->pendingAllAttributesList(); 00774 00775 QMap < int /*offline attr*/, int /*remote attr*/ > attrLookup; 00776 // NOTE: use size of remoteAttrs, as offlineAttrs can have new attributes not yet synced 00777 for ( int i = 0; i < remoteAttrs.size(); i++ ) 00778 { 00779 attrLookup.insert( offlineAttrs.at( i ), remoteAttrs.at( i ) ); 00780 } 00781 00782 return attrLookup; 00783 } 00784 00785 void QgsOfflineEditing::showWarning( const QString& message ) 00786 { 00787 QMessageBox::warning( NULL, tr( "Offline Editing Plugin" ), message ); 00788 } 00789 00790 sqlite3* QgsOfflineEditing::openLoggingDb() 00791 { 00792 sqlite3* db = NULL; 00793 QString dbPath = QgsProject::instance()->readEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH ); 00794 if ( !dbPath.isEmpty() ) 00795 { 00796 int rc = sqlite3_open( dbPath.toStdString().c_str(), &db ); 00797 if ( rc != SQLITE_OK ) 00798 { 00799 showWarning( tr( "Could not open the spatialite logging database" ) ); 00800 sqlite3_close( db ); 00801 db = NULL; 00802 } 00803 } 00804 return db; 00805 } 00806 00807 int QgsOfflineEditing::getOrCreateLayerId( sqlite3* db, const QString& qgisLayerId ) 00808 { 00809 QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId ); 00810 int layerId = sqlQueryInt( db, sql, -1 ); 00811 if ( layerId == -1 ) 00812 { 00813 // next layer id 00814 sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'"; 00815 int newLayerId = sqlQueryInt( db, sql, -1 ); 00816 00817 // insert layer 00818 sql = QString( "INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId ); 00819 sqlExec( db, sql ); 00820 00821 // increase layer_id 00822 // TODO: use trigger for auto increment? 00823 sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 ); 00824 sqlExec( db, sql ); 00825 00826 layerId = newLayerId; 00827 } 00828 00829 return layerId; 00830 } 00831 00832 int QgsOfflineEditing::getCommitNo( sqlite3* db ) 00833 { 00834 QString sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'"; 00835 return sqlQueryInt( db, sql, -1 ); 00836 } 00837 00838 void QgsOfflineEditing::increaseCommitNo( sqlite3* db ) 00839 { 00840 QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 ); 00841 sqlExec( db, sql ); 00842 } 00843 00844 void QgsOfflineEditing::addFidLookup( sqlite3* db, int layerId, QgsFeatureId offlineFid, QgsFeatureId remoteFid ) 00845 { 00846 QString sql = QString( "INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid ); 00847 sqlExec( db, sql ); 00848 } 00849 00850 QgsFeatureId QgsOfflineEditing::remoteFid( sqlite3* db, int layerId, QgsFeatureId offlineFid ) 00851 { 00852 QString sql = QString( "SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid ); 00853 return sqlQueryInt( db, sql, -1 ); 00854 } 00855 00856 QgsFeatureId QgsOfflineEditing::offlineFid( sqlite3* db, int layerId, QgsFeatureId remoteFid ) 00857 { 00858 QString sql = QString( "SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid ); 00859 return sqlQueryInt( db, sql, -1 ); 00860 } 00861 00862 bool QgsOfflineEditing::isAddedFeature( sqlite3* db, int layerId, QgsFeatureId fid ) 00863 { 00864 QString sql = QString( "SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid ); 00865 return ( sqlQueryInt( db, sql, 0 ) > 0 ); 00866 } 00867 00868 int QgsOfflineEditing::sqlExec( sqlite3* db, const QString& sql ) 00869 { 00870 char * errmsg; 00871 int rc = sqlite3_exec( db, sql.toUtf8(), NULL, NULL, &errmsg ); 00872 if ( rc != SQLITE_OK ) 00873 { 00874 showWarning( errmsg ); 00875 } 00876 return rc; 00877 } 00878 00879 int QgsOfflineEditing::sqlQueryInt( sqlite3* db, const QString& sql, int defaultValue ) 00880 { 00881 sqlite3_stmt* stmt = NULL; 00882 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK ) 00883 { 00884 showWarning( sqlite3_errmsg( db ) ); 00885 return defaultValue; 00886 } 00887 00888 int value = defaultValue; 00889 int ret = sqlite3_step( stmt ); 00890 if ( ret == SQLITE_ROW ) 00891 { 00892 value = sqlite3_column_int( stmt, 0 ); 00893 } 00894 sqlite3_finalize( stmt ); 00895 00896 return value; 00897 } 00898 00899 QList<int> QgsOfflineEditing::sqlQueryInts( sqlite3* db, const QString& sql ) 00900 { 00901 QList<int> values; 00902 00903 sqlite3_stmt* stmt = NULL; 00904 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK ) 00905 { 00906 showWarning( sqlite3_errmsg( db ) ); 00907 return values; 00908 } 00909 00910 int ret = sqlite3_step( stmt ); 00911 while ( ret == SQLITE_ROW ) 00912 { 00913 values << sqlite3_column_int( stmt, 0 ); 00914 00915 ret = sqlite3_step( stmt ); 00916 } 00917 sqlite3_finalize( stmt ); 00918 00919 return values; 00920 } 00921 00922 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( sqlite3* db, const QString& sql ) 00923 { 00924 QList<QgsField> values; 00925 00926 sqlite3_stmt* stmt = NULL; 00927 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK ) 00928 { 00929 showWarning( sqlite3_errmsg( db ) ); 00930 return values; 00931 } 00932 00933 int ret = sqlite3_step( stmt ); 00934 while ( ret == SQLITE_ROW ) 00935 { 00936 QgsField field( QString(( const char* )sqlite3_column_text( stmt, 0 ) ), 00937 ( QVariant::Type )sqlite3_column_int( stmt, 1 ), 00938 "", // typeName 00939 sqlite3_column_int( stmt, 2 ), 00940 sqlite3_column_int( stmt, 3 ), 00941 QString(( const char* )sqlite3_column_text( stmt, 4 ) ) ); 00942 values << field; 00943 00944 ret = sqlite3_step( stmt ); 00945 } 00946 sqlite3_finalize( stmt ); 00947 00948 return values; 00949 } 00950 00951 QgsFeatureIds QgsOfflineEditing::sqlQueryFeaturesRemoved( sqlite3* db, const QString& sql ) 00952 { 00953 QgsFeatureIds values; 00954 00955 sqlite3_stmt* stmt = NULL; 00956 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK ) 00957 { 00958 showWarning( sqlite3_errmsg( db ) ); 00959 return values; 00960 } 00961 00962 int ret = sqlite3_step( stmt ); 00963 while ( ret == SQLITE_ROW ) 00964 { 00965 values << sqlite3_column_int( stmt, 0 ); 00966 00967 ret = sqlite3_step( stmt ); 00968 } 00969 sqlite3_finalize( stmt ); 00970 00971 return values; 00972 } 00973 00974 QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges( sqlite3* db, const QString& sql ) 00975 { 00976 AttributeValueChanges values; 00977 00978 sqlite3_stmt* stmt = NULL; 00979 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK ) 00980 { 00981 showWarning( sqlite3_errmsg( db ) ); 00982 return values; 00983 } 00984 00985 int ret = sqlite3_step( stmt ); 00986 while ( ret == SQLITE_ROW ) 00987 { 00988 AttributeValueChange change; 00989 change.fid = sqlite3_column_int( stmt, 0 ); 00990 change.attr = sqlite3_column_int( stmt, 1 ); 00991 change.value = QString(( const char* )sqlite3_column_text( stmt, 2 ) ); 00992 values << change; 00993 00994 ret = sqlite3_step( stmt ); 00995 } 00996 sqlite3_finalize( stmt ); 00997 00998 return values; 00999 } 01000 01001 QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges( sqlite3* db, const QString& sql ) 01002 { 01003 GeometryChanges values; 01004 01005 sqlite3_stmt* stmt = NULL; 01006 if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK ) 01007 { 01008 showWarning( sqlite3_errmsg( db ) ); 01009 return values; 01010 } 01011 01012 int ret = sqlite3_step( stmt ); 01013 while ( ret == SQLITE_ROW ) 01014 { 01015 GeometryChange change; 01016 change.fid = sqlite3_column_int( stmt, 0 ); 01017 change.geom_wkt = QString(( const char* )sqlite3_column_text( stmt, 1 ) ); 01018 values << change; 01019 01020 ret = sqlite3_step( stmt ); 01021 } 01022 sqlite3_finalize( stmt ); 01023 01024 return values; 01025 } 01026 01027 void QgsOfflineEditing::layerAdded( QgsMapLayer* layer ) 01028 { 01029 // detect offline layer 01030 if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() ) 01031 { 01032 // enable logging 01033 connect( layer, SIGNAL( committedAttributesAdded( const QString&, const QList<QgsField>& ) ), 01034 this, SLOT( committedAttributesAdded( const QString&, const QList<QgsField>& ) ) ); 01035 connect( layer, SIGNAL( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ), 01036 this, SLOT( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ) ); 01037 connect( layer, SIGNAL( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ), 01038 this, SLOT( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ) ); 01039 connect( layer, SIGNAL( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ), 01040 this, SLOT( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ) ); 01041 connect( layer, SIGNAL( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ), 01042 this, SLOT( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ) ); 01043 } 01044 } 01045 01046 void QgsOfflineEditing::committedAttributesAdded( const QString& qgisLayerId, const QList<QgsField>& addedAttributes ) 01047 { 01048 sqlite3* db = openLoggingDb(); 01049 if ( db == NULL ) 01050 { 01051 return; 01052 } 01053 01054 // insert log 01055 int layerId = getOrCreateLayerId( db, qgisLayerId ); 01056 int commitNo = getCommitNo( db ); 01057 01058 for ( QList<QgsField>::const_iterator it = addedAttributes.begin(); it != addedAttributes.end(); ++it ) 01059 { 01060 QgsField field = *it; 01061 QString sql = QString( "INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" ) 01062 .arg( layerId ) 01063 .arg( commitNo ) 01064 .arg( field.name() ) 01065 .arg( field.type() ) 01066 .arg( field.length() ) 01067 .arg( field.precision() ) 01068 .arg( field.comment() ); 01069 sqlExec( db, sql ); 01070 } 01071 01072 increaseCommitNo( db ); 01073 sqlite3_close( db ); 01074 } 01075 01076 void QgsOfflineEditing::committedFeaturesAdded( const QString& qgisLayerId, const QgsFeatureList& addedFeatures ) 01077 { 01078 sqlite3* db = openLoggingDb(); 01079 if ( db == NULL ) 01080 { 01081 return; 01082 } 01083 01084 // insert log 01085 int layerId = getOrCreateLayerId( db, qgisLayerId ); 01086 01087 // get new feature ids from db 01088 QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( qgisLayerId ); 01089 QgsDataSourceURI uri = QgsDataSourceURI( layer->source() ); 01090 01091 // only store feature ids 01092 QString sql = QString( "SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( uri.table() ).arg( addedFeatures.size() ); 01093 QList<int> newFeatureIds = sqlQueryInts( db, sql ); 01094 for ( int i = newFeatureIds.size() - 1; i >= 0; i-- ) 01095 { 01096 QString sql = QString( "INSERT INTO 'log_added_features' VALUES ( %1, %2 )" ) 01097 .arg( layerId ) 01098 .arg( newFeatureIds.at( i ) ); 01099 sqlExec( db, sql ); 01100 } 01101 01102 sqlite3_close( db ); 01103 } 01104 01105 void QgsOfflineEditing::committedFeaturesRemoved( const QString& qgisLayerId, const QgsFeatureIds& deletedFeatureIds ) 01106 { 01107 sqlite3* db = openLoggingDb(); 01108 if ( db == NULL ) 01109 { 01110 return; 01111 } 01112 01113 // insert log 01114 int layerId = getOrCreateLayerId( db, qgisLayerId ); 01115 01116 for ( QgsFeatureIds::const_iterator it = deletedFeatureIds.begin(); it != deletedFeatureIds.end(); ++it ) 01117 { 01118 if ( isAddedFeature( db, layerId, *it ) ) 01119 { 01120 // remove from added features log 01121 QString sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( *it ); 01122 sqlExec( db, sql ); 01123 } 01124 else 01125 { 01126 QString sql = QString( "INSERT INTO 'log_removed_features' VALUES ( %1, %2)" ) 01127 .arg( layerId ) 01128 .arg( *it ); 01129 sqlExec( db, sql ); 01130 } 01131 } 01132 01133 sqlite3_close( db ); 01134 } 01135 01136 void QgsOfflineEditing::committedAttributeValuesChanges( const QString& qgisLayerId, const QgsChangedAttributesMap& changedAttrsMap ) 01137 { 01138 sqlite3* db = openLoggingDb(); 01139 if ( db == NULL ) 01140 { 01141 return; 01142 } 01143 01144 // insert log 01145 int layerId = getOrCreateLayerId( db, qgisLayerId ); 01146 int commitNo = getCommitNo( db ); 01147 01148 for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit ) 01149 { 01150 QgsFeatureId fid = cit.key(); 01151 if ( isAddedFeature( db, layerId, fid ) ) 01152 { 01153 // skip added features 01154 continue; 01155 } 01156 QgsAttributeMap attrMap = cit.value(); 01157 for ( QgsAttributeMap::const_iterator it = attrMap.begin(); it != attrMap.end(); ++it ) 01158 { 01159 QString sql = QString( "INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" ) 01160 .arg( layerId ) 01161 .arg( commitNo ) 01162 .arg( fid ) 01163 .arg( it.key() ) // attr 01164 .arg( it.value().toString() ); // value 01165 sqlExec( db, sql ); 01166 } 01167 } 01168 01169 increaseCommitNo( db ); 01170 sqlite3_close( db ); 01171 } 01172 01173 void QgsOfflineEditing::committedGeometriesChanges( const QString& qgisLayerId, const QgsGeometryMap& changedGeometries ) 01174 { 01175 sqlite3* db = openLoggingDb(); 01176 if ( db == NULL ) 01177 { 01178 return; 01179 } 01180 01181 // insert log 01182 int layerId = getOrCreateLayerId( db, qgisLayerId ); 01183 int commitNo = getCommitNo( db ); 01184 01185 for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it ) 01186 { 01187 QgsFeatureId fid = it.key(); 01188 if ( isAddedFeature( db, layerId, fid ) ) 01189 { 01190 // skip added features 01191 continue; 01192 } 01193 QgsGeometry geom = it.value(); 01194 QString sql = QString( "INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" ) 01195 .arg( layerId ) 01196 .arg( commitNo ) 01197 .arg( fid ) 01198 .arg( geom.exportToWkt() ); 01199 sqlExec( db, sql ); 01200 01201 // TODO: use WKB instead of WKT? 01202 } 01203 01204 increaseCommitNo( db ); 01205 sqlite3_close( db ); 01206 }