[QGIS-Developer] QGIS/OGR: FeatureIds reassigned on write to data provider?

Sandro Mani manisandro at gmail.com
Mon Jul 10 04:30:58 PDT 2017


On 06.07.2017 11:00, Sandro Mani wrote:
>
>
> On 06.07.2017 10:57, Even Rouault wrote:
>>
>> On jeudi 6 juillet 2017 10:45:21 CEST Sandro Mani wrote:
>>
>> > On 05.07.2017 14:56, Sandro Mani wrote:
>>
>> > >> That could potentially be done using the current OGR API. Basically
>>
>> > >> in pseudo code, to execute before executing REPACK
>>
>> > >>
>>
>> > >> OGR_L_ResetReading(layer)
>>
>> > >>
>>
>> > >> char** ignored_fields = CSLAddString(NULL, "OGR_GEOMETRY" );
>>
>> > >>
>>
>> > >> OGR_L_SetIgnoredFields( layer, ignored_fields); // for performance.
>>
>> > >>
>>
>> > >> CSLDestroy(ignored_fields);
>>
>> > >>
>>
>> > >> OGR_L_SetAttributeFilter( layer, NULL )
>>
>> > >>
>>
>> > >> OGR_L_SetSpatialFilter( layer, NULL )
>>
>> > >>
>>
>> > >> std::map<GIntBig, GIntBig> mapOldIdToNewId;
>>
>> > >>
>>
>> > >> GIntBig newId = 0;
>>
>> > >>
>>
>> > >> while( feature = OGR_L_GetNextFeature(layer) )
>>
>> > >>
>>
>> > >> {
>>
>> > >>
>>
>> > >> mapOldIdToNewId[OGR_F_GetFID(feature)] = newId;
>>
>> > >>
>>
>> > >> newId ++;
>>
>> > >>
>>
>> > >> OGR_F_destroy(feature);
>>
>> > >>
>>
>> > >> }
>>
>> > >>
>>
>> > >> OGR_L_SetIgnoredFields( layer, NULL );
>>
>> > >
>>
>> > > Ok cool, that sounds like a plan - I'll give it a shot.
>>
>> >
>>
>> > Hmm so while on the provider side it works well, for the geometry
>>
>> > checker it is turning out to be pretty hard to deal with the changing
>>
>> > feature ids (just to cite one example: error fixed by merging geometry
>>
>> > of feature 10 with that of 11, results in {deleted: 10, updated: 11},
>>
>> > but after the feature id adjustment this would read {deleted: 10,
>>
>> > updated: 10}, meaning one would need to keep track that deleted: 10
>>
>> > refers to the old featureid). Not saying that it isn't doable, but the
>>
>> > complexitiy of properly handling the feature id changes is non-trivial.
>>
>> >
>>
>> > So, other suggestion: any objections if I add a method to
>>
>> > QgsVectorDataProvider to temporarily freeze repacking? I could also add
>>
>> > a notification informing the user that the shapefile should not be used
>>
>> > in other applications while repacking is frozen.
>>
>> My feeling is that QgsOgrDataProvider is already complicated enough 
>> with its existing tricks. I'm not sure this temporary freeze 
>> repacking is a right move (how would you decide when you do it ? and 
>> I'm pretty sure users will not get it, or will have issues if they 
>> start an algorithm with an external tool that requires packed shapefiles)
>>
> It would be something I would call explicitly while the geometry 
> checker is active, I think it is safe to assume that users don't 
> expect that they can operate on the shapefile while the geometry 
> checker is processing it.  It would not add much complexity to the 
> provider, just a flag that repacking is frozen, and a setter to set 
> that flag. When the flag is set to false, the shapefile is repacked.
>
I did some playing around and came up with some FeatureId mapping code 
which is pretty space efficient (basically requiring just 
3*nDeletedFeatures entries), see below.

The code however assumes that the gaps in the fid-sequence are filled by 
decreasing the fids after the gap, which may be pretty specific to the 
OGR/Shapefile behaviour. Hence in my view such code should really belong 
into the OGR provider (or perhaps even ogr itself) and not in the 
plugin. Thoughts?

Sandro

-------

#include <algorithm>
#include <iostream>
#include <QMap>
#include <QVector>

typedef int QgsFeatureId;


class FeatureMapping  {

public:
   QgsFeatureId stableToProvider(QgsFeatureId stableId) const
   {
     if(mDeletedStableIds.contains(stableId)){
       return -1;
     }
     // providerId = stableId - nDeletedBeforeStableId
     int nDeletedBeforeStableId = std::lower_bound(mDeletedStableIds.begin(), mDeletedStableIds.end(), stableId) - mDeletedStableIds.begin();
     return stableId - nDeletedBeforeStableId;
   }

   QgsFeatureId providerToStable(QgsFeatureId providerId) const
   {
     int i = 0, n = mProviderToStableOffsetKeys.size(), offset = 0;
     while(i < n && mProviderToStableOffsetKeys[i] <= providerId) {
       offset += mProviderToStableOffsetValues[i++];
     }
     return providerId + offset;
   }

   void updateFeatureIdMap(const QList<QgsFeatureId> &deletedIds)
   {
     for(QgsFeatureId fid : deletedIds) {
       mDeletedStableIds.append(providerToStable(fid));
     }
     std::sort(mDeletedStableIds.begin(), mDeletedStableIds.end());
     int n = mProviderToStableOffsetKeys.size();
     for(QgsFeatureId fid : deletedIds) {
       int pos = std::lower_bound(mProviderToStableOffsetKeys.begin(), mProviderToStableOffsetKeys.end(), fid) - mProviderToStableOffsetKeys.begin();
       if(pos < n && mProviderToStableOffsetKeys[pos] == fid) {
         mProviderToStableOffsetValues[pos] += 1;
       } else {
         mProviderToStableOffsetKeys.insert(pos, fid);
         mProviderToStableOffsetValues.insert(pos, 1);
         ++n;
       }
       if(pos + 1 != n && mProviderToStableOffsetKeys[pos+1] -1 == fid) {
         mProviderToStableOffsetValues[pos] += mProviderToStableOffsetValues[pos+1];
         mProviderToStableOffsetKeys.removeAt(pos + 1);
         mProviderToStableOffsetValues.removeAt(pos + 1);
         --n;
       }
       ++pos;
       while(pos < n) {
           mProviderToStableOffsetKeys[pos++] -= 1;
       }
     }
     std::cout << "offsets = {";
     for(int i = 0, n = mProviderToStableOffsetKeys.size(); i < n; ++i) {
       std::cout << mProviderToStableOffsetKeys[i] << ": " << mProviderToStableOffsetValues[i] << ", ";
     }
     std::cout << "\b\b}" << std::endl;
   }
   int numDeleted() const{ return mDeletedStableIds.size(); }
private:
   QVector<QgsFeatureId> mProviderToStableOffsetKeys;
   QVector<int> mProviderToStableOffsetValues;
   QVector<QgsFeatureId> mDeletedStableIds;
};

void printMapping(const FeatureMapping& mapping) {
   std::cout << "------" << std::endl;
   for(int i = 0; i < 10; ++i) {
     std::cout << i << " -> " << mapping.stableToProvider(i) << std::endl;
   }
   std::cout << "------" << std::endl;
   for(int i = 0, n = 10 - mapping.numDeleted(); i < n; ++i) {
     std::cout << mapping.providerToStable(i) << " <- " << i << std::endl;
   }
   std::cout << "------" << std::endl;
}

int main(int argc, char* argv[]){
   FeatureMapping mapping;

   printMapping(mapping);
   while(true) {
     std::cout << "> ";
     std::string command;
     std::getline(std::cin, command);
     if(command.empty()) {
       continue;
     }else if(command[0] == 'q') {
       break;
     }else if(command[0] == 'd') {
       QgsFeatureId num = std::atoi(command.substr(1).c_str());
       std::cout << "* Delete: stable " << num << ", provider: " << mapping.stableToProvider(num) << std::endl;
       mapping.updateFeatureIdMap({mapping.stableToProvider(num)});
       printMapping(mapping);
     } else {
       std::cout << "Unknown command" << std::endl;
     }
   }

   return 0;
}



-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osgeo.org/pipermail/qgis-developer/attachments/20170710/c57458cf/attachment-0001.html>


More information about the QGIS-Developer mailing list