GMsg VectorBuffer::EraseFeatureFromInteraction(
        const feature_ptr &src, const string &proj,
        const std::unordered_set<long> &targets,
        const std::unordered_set<long> &immunes)
{
    GRect ext = MapnikUtils::Box2d2GRect(src->envelope());
    unordered_map<long, feature_ptr> fs = GetFeaturesInRect(
                proj, ext, _config_app().VIEW_VECTORBUFFER_SELECTION_MAX);
    unordered_map<long, feature_ptr> edited_in_clipping;
    unordered_set<long> removed_in_clipping;
    for (unordered_map<long, feature_ptr>::const_iterator it = fs.begin();
         it != fs.end(); it++)
    {
        feature_ptr f = it->second;
        if (immunes.find(f->id()) != immunes.end()) continue;
        if (!targets.empty() && targets.find(f->id()) == targets.end()) continue;
        if (MapnikUtils::contains(
                    src, f, wkbPolygon, _book->GetGeomType(), proj, _layer->proj()))
        {
            removed_in_clipping.insert(it->first);
        }
        else if (MapnikUtils::intersects(
                    src, f, wkbPolygon, _book->GetGeomType(), proj, _layer->proj()))
        {
            feature_ptr clipped = MapnikUtils::difference(
                        f, src, _book->GetGeomType(), wkbPolygon, _layer->proj(), proj);
            if (clipped != nullptr) {
                edited_in_clipping.insert(pair<long, feature_ptr>(it->first, clipped));
            }
        }
    }

    unsigned int num = removed_in_clipping.size() + edited_in_clipping.size();
    if (num == 0) return G_NOERR;
    _undoStack->beginMacro(num == 1 ? "Erase feature" : "Erase features");
    for (unordered_set<long>::const_iterator it = removed_in_clipping.begin();
         it != removed_in_clipping.end(); it++)
    {
        _undoStack->push(new VBCommandDeleteFeature(this, *it));
    }
    for (unordered_map<long, feature_ptr>::const_iterator it = edited_in_clipping.begin();
         it != edited_in_clipping.end(); it++)
    {
        long fid = it->first;
        feature_ptr f = it->second;
        Q_ASSERT(_deletedFeatures.find(fid) == _deletedFeatures.end());
        f->put<value_unicode_string>(
                    _config_app().VIEW_VECTORBUFFER_FIELD_EDITED,
                    transcoder("utf-8").transcode("edited"));
        _undoStack->push(new VBCommandEditFeature(this, fid, f));
    }
    _undoStack->endMacro();
    return G_NOERR;
}
GMsg VectorBuffer::SplitFeatureFromInteraction(
        const feature_ptr &src, const string &proj)
{
    GRect ext = MapnikUtils::Box2d2GRect(src->envelope());
    unordered_map<long, feature_ptr> fs = GetFeaturesInRect(
                proj, ext, _config_app().VIEW_VECTORBUFFER_SELECTION_MAX);
    unordered_map<long, vector<feature_ptr> > edited_in_splitting;
    GMsg retmsg;
    for (unordered_map<long, feature_ptr>::const_iterator it = fs.begin();
         it != fs.end(); it++)
    {
        feature_ptr f = it->second;
        if (MapnikUtils::intersects(
                    src, f, wkbLineString, _book->GetGeomType(), proj, _layer->proj()))
        {
            vector<mapnik::feature_ptr> splitted;
            GMsg msg = MapnikUtils::split(f, src, _book->GetGeomType(), splitted, _layer->proj(), proj);
            if (msg != G_NOERR) {
                if (msg.is_error()) retmsg = msg;
            } else {
                edited_in_splitting.insert(pair<long, vector<feature_ptr> >(it->first, splitted));
            }
        }
    }

    unsigned int num = edited_in_splitting.size();
    if (num == 0) return retmsg;
    _undoStack->beginMacro(num == 1 ? "Split feature" : "Split features");
    for (unordered_map<long, vector<feature_ptr> >::const_iterator it = edited_in_splitting.begin();
         it != edited_in_splitting.end(); it++)
    {
        long fid = it->first;
        vector<feature_ptr> fs = it->second;
        Q_ASSERT(_deletedFeatures.find(fid) == _deletedFeatures.end());
        _undoStack->push(new VBCommandDeleteFeature(this, fid));
        for (unsigned int i = 0; i != fs.size(); i++) {
            feature_ptr f = fs.at(i);
            f->set_id(_fidCandidate);
            f->put<value_unicode_string>(
                        _config_app().VIEW_VECTORBUFFER_FIELD_EDITED,
                        transcoder("utf-8").transcode("edited"));
            _undoStack->push(new VBCommandAddFeature(this, _fidCandidate, f));
            _fidCandidate++;
        }
    }
    _undoStack->endMacro();
    return retmsg;
}