QGIS/src/core/qgsvectorlayer.cpp
jef 43278d720b handling vector data geometry and attribute updates refactored
QgsVectorLayer:
  - move attribute part of editing to vector layer class and unify with geometry handling:
    * remove commitAttributeChanges(), addedFeatures(), deletedFeatureIds(), changedAttributes()
      and replace with changeAttributeValue(), deleteFeature(), addAttribute()
      and deleteAttribute()
    * add pendingFields(), pendingAttributeList(), pendingFeatureCount()
    * emit signals on start editing and commit, change of attribute values, adding/deleting of
      attributes and layer or feature removal (currently used in the attribute table)
  - new commitErrors() method to query errors from commitChanges()
  - replaced featuresInRectangle with select/getNextFeature combo
  - edit types added to support more input widgets and input constraints

QgsFeature:
  - remove update aware ctor
  - unify geometry handling in ctors

QgsVectorDataProvider:
  - add QVariant::Type to supportNativeTypes()

QgisApp:
  - add instance() method to query QgisApp object
  - replace code at various place to use it instead of passing the pointer
    arround or searching it in the widget tree.
  - move toggleEditing() code from the legend here

QgsAttributeTable/QgsAttributeTableDisplay:
  - move attribute table creation legend here
  - make attribute table dockable (from Tim)
  - most editing logic moved to QgsVectorLayer
  - adding/deleting attributes moved to QgsVectorLayerProperties

QgsIdentifyResults:
  - add support for attribute editing when it edit mode

QgsVectorLayerProperties:
  add a new tab to show attribute list:
    * start/stop editing
    * add/delete attributes
    * assign edit type to attributes (unique values, value map, ranges)

QgsAttributeDialog:
  add support for attribute edit types:
   * selection from unique value render classes (combobox)
   * selection from unique values of existing features (combobox or line edits with completion)
   * spinboxes for ranges

QgsPostgresProvider:
 - use read-only connection for cursors and read-write connection for updates
 - updated native types

QgsOgrProvider:
 - remove unused references to GEOS geometry factory
 - updated native types


git-svn-id: http://svn.osgeo.org/qgis/trunk@9092 c8812cc2-4d05-0410-92ff-de0c093fc19c
2008-08-20 12:15:14 +00:00

3465 lines
90 KiB
C++

/***************************************************************************
qgsvectorlayer.cpp
This class implements a generic means to display vector layers. The features
and attributes are read from the data store using a "data provider" plugin.
QgsVectorLayer can be used with any data store for which an appropriate
plugin is available.
-------------------
begin : Oct 29, 2003
copyright : (C) 2003 by Gary E.Sherman
email : sherman at mrcc.com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
/* $Id$ */
#include <cassert>
#include <cfloat>
#include <cstring>
#include <climits>
#include <cmath>
#include <iosfwd>
#include <iostream>
#include <limits>
#include <memory>
#include <set>
#include <sstream>
#include <utility>
#include <QImage>
#include <QPainter>
#include <QPainterPath>
#include <QPolygonF>
#include <QSettings>
#include <QString>
#include "qgsvectorlayer.h"
// renderers
#include "qgscontinuouscolorrenderer.h"
#include "qgsgraduatedsymbolrenderer.h"
#include "qgsrenderer.h"
#include "qgssinglesymbolrenderer.h"
#include "qgsuniquevaluerenderer.h"
#include "qgsattributeaction.h"
#include "qgis.h" //for globals
#include "qgsapplication.h"
#include "qgscoordinatetransform.h"
#include "qgsfeature.h"
#include "qgsfield.h"
#include "qgsgeometry.h"
#include "qgslabel.h"
#include "qgslogger.h"
#include "qgsmaptopixel.h"
#include "qgspoint.h"
#include "qgsproviderregistry.h"
#include "qgsrect.h"
#include "qgsrendercontext.h"
#include "qgssinglesymbolrenderer.h"
#include "qgsspatialrefsys.h"
#include "qgsvectordataprovider.h"
#ifdef Q_WS_X11
#include "qgsclipper.h"
#endif
#ifdef TESTPROVIDERLIB
#include <dlfcn.h>
#endif
static const char * const ident_ = "$Id$";
// typedef for the QgsDataProvider class factory
typedef QgsDataProvider * create_it(const QString* uri);
QgsVectorLayer::QgsVectorLayer(QString vectorLayerPath,
QString baseName,
QString providerKey,
bool loadDefaultStyleFlag )
: QgsMapLayer(VECTOR, baseName, vectorLayerPath),
mUpdateThreshold(0), // XXX better default value?
mDataProvider(NULL),
mProviderKey(providerKey),
mEditable(false),
mModified(false),
mRenderer(0),
mLabel(0),
mLabelOn(false),
mMaxUpdatedIndex(-1),
mFetching(false)
{
mActions = new QgsAttributeAction;
// if we're given a provider type, try to create and bind one to this layer
if ( ! mProviderKey.isEmpty() )
{
setDataProvider( mProviderKey );
}
if(mValid)
{
// check if there is a default style / propertysheet defined
// for this layer and if so apply it
//
if ( loadDefaultStyleFlag )
{
bool defaultLoadedFlag = false;
loadDefaultStyle( defaultLoadedFlag );
if ( !defaultLoadedFlag )
{
setCoordinateSystem();
// add single symbol renderer as default
QgsSingleSymbolRenderer *renderer = new QgsSingleSymbolRenderer(vectorType());
setRenderer(renderer);
}
}
else // Otherwise use some very basic defaults
{
setCoordinateSystem();
// add single symbol renderer as default
QgsSingleSymbolRenderer *renderer = new QgsSingleSymbolRenderer(vectorType());
setRenderer(renderer);
}
// Get the update threshold from user settings. We
// do this only on construction to avoid the penality of
// fetching this each time the layer is drawn. If the user
// changes the threshold from the preferences dialog, it will
// have no effect on existing layers
// TODO: load this setting somewhere else [MD]
//QSettings settings;
//mUpdateThreshold = settings.readNumEntry("Map/updateThreshold", 1000);
}
} // QgsVectorLayer ctor
QgsVectorLayer::~QgsVectorLayer()
{
QgsDebugMsg("In QgsVectorLayer destructor");
emit layerDeleted();
mValid=false;
if (mRenderer)
{
delete mRenderer;
}
// delete the provider object
delete mDataProvider;
delete mLabel;
// Destroy any cached geometries and clear the references to them
deleteCachedGeometries();
delete mActions;
}
QString QgsVectorLayer::storageType() const
{
if (mDataProvider)
{
return mDataProvider->storageType();
}
return 0;
}
QString QgsVectorLayer::capabilitiesString() const
{
if (mDataProvider)
{
return mDataProvider->capabilitiesString();
}
return 0;
}
QString QgsVectorLayer::dataComment() const
{
if (mDataProvider)
{
return mDataProvider->dataComment();
}
return QString();
}
QString QgsVectorLayer::providerType() const
{
return mProviderKey;
}
/**
* sets the preferred display field based on some fuzzy logic
*/
void QgsVectorLayer::setDisplayField(QString fldName)
{
// If fldName is provided, use it as the display field, otherwise
// determine the field index for the feature column of the identify
// dialog. We look for fields containing "name" first and second for
// fields containing "id". If neither are found, the first field
// is used as the node.
QString idxName="";
QString idxId="";
if(!fldName.isEmpty())
{
mDisplayField = fldName;
}
else
{
const QgsFieldMap &fields = pendingFields();
int fieldsSize = fields.size();
for (QgsFieldMap::const_iterator it = fields.begin(); it != fields.end(); ++it)
{
QString fldName = it.value().name();
QgsDebugMsg("Checking field " + fldName + " of " + QString::number(fieldsSize) + " total");
// Check the fields and keep the first one that matches.
// We assume that the user has organized the data with the
// more "interesting" field names first. As such, name should
// be selected before oldname, othername, etc.
if (fldName.indexOf("name", false) > -1)
{
if(idxName.isEmpty())
{
idxName = fldName;
}
}
if (fldName.indexOf("descrip", false) > -1)
{
if(idxName.isEmpty())
{
idxName = fldName;
}
}
if (fldName.indexOf("id", false) > -1)
{
if(idxId.isEmpty())
{
idxId = fldName;
}
}
}
//if there were no fields in the dbf just return - otherwise qgis segfaults!
if (fieldsSize == 0)
return;
if (idxName.length() > 0)
{
mDisplayField = idxName;
}
else
{
if (idxId.length() > 0)
{
mDisplayField = idxId;
}
else
{
mDisplayField = fields[0].name();
}
}
}
}
void QgsVectorLayer::drawLabels(QgsRenderContext& renderContext)
{
QPainter* thePainter = renderContext.painter();
if(!thePainter)
{
return;
}
drawLabels(thePainter, renderContext.extent(), &(renderContext.mapToPixel()), renderContext.coordTransform(), 1.0 / renderContext.rasterScaleFactor());
}
// NOTE this is a temporary method added by Tim to prevent label clipping
// which was occurring when labeller was called in the main draw loop
// This method will probably be removed again in the near future!
void QgsVectorLayer::drawLabels(QPainter * p, const QgsRect& viewExtent, const QgsMapToPixel* theMapToPixelTransform, const QgsCoordinateTransform* ct, double scale)
{
QgsDebugMsg("Starting draw of labels");
if (mRenderer && mLabelOn)
{
QgsAttributeList attributes = mRenderer->classificationAttributes();
// Add fields required for labels
mLabel->addRequiredFields( attributes );
QgsDebugMsg("Selecting features based on view extent");
int featureCount = 0;
try
{
// select the records in the extent. The provider sets a spatial filter
// and sets up the selection set for retrieval
select(attributes, viewExtent, true);
QgsFeature fet;
while( getNextFeature(fet) )
{
if(mRenderer->willRenderFeature(&fet))
{
bool sel = mSelectedFeatureIds.contains(fet.featureId());
mLabel->renderLabel( p, viewExtent, ct, theMapToPixelTransform, fet, sel, 0, scale);
}
featureCount++;
}
}
catch (QgsCsException &e)
{
Q_UNUSED(e);
QgsLogger::critical("Error projecting label locations, caught in " + QString(__FILE__) + ", line " +QString(__LINE__));
}
#ifdef QGISDEBUG
QgsLogger::debug("Total features processed", featureCount, 1, __FILE__, __FUNCTION__, __LINE__);
#endif
// XXX Something in our draw event is triggering an additional draw event when resizing [TE 01/26/06]
// XXX Calling this will begin processing the next draw event causing image havoc and recursion crashes.
//qApp->processEvents();
}
}
unsigned char* QgsVectorLayer::drawLineString(
unsigned char *feature,
QPainter* p,
const QgsMapToPixel* mtp,
const QgsCoordinateTransform* ct,
bool drawingToEditingCanvas)
{
unsigned char *ptr = feature + 5;
unsigned int wkbType = *((int*)(feature+1));
unsigned int nPoints = *((int*)ptr);
ptr = feature + 9;
bool hasZValue = (wkbType == QGis::WKBLineString25D);
std::vector<double> x(nPoints);
std::vector<double> y(nPoints);
std::vector<double> z(nPoints, 0.0);
// Extract the points from the WKB format into the x and y vectors.
for (register unsigned int i = 0; i < nPoints; ++i)
{
x[i] = *((double *) ptr);
ptr += sizeof(double);
y[i] = *((double *) ptr);
ptr += sizeof(double);
if (hasZValue) // ignore Z value
ptr += sizeof(double);
}
// Transform the points into map coordinates (and reproject if
// necessary)
transformPoints(x, y, z, mtp, ct);
#if defined(Q_WS_X11)
// Work around a +/- 32768 limitation on coordinates in X11
// Look through the x and y coordinates and see if there are any
// that need trimming. If one is found, there's no need to look at
// the rest of them so end the loop at that point.
for (register unsigned int i = 0; i < nPoints; ++i)
{
if (std::abs(x[i]) > QgsClipper::maxX ||
std::abs(y[i]) > QgsClipper::maxY)
{
QgsClipper::trimFeature(x, y, true); // true = polyline
nPoints = x.size(); // trimming may change nPoints.
break;
}
}
#endif
// set up QPolygonF class with transformed points
QPolygonF pa(nPoints);
for (register unsigned int i = 0; i < nPoints; ++i)
{
pa[i].setX(x[i]);
pa[i].setY(y[i]);
}
#ifdef QGISDEBUGVERBOSE
// this is only used for verbose debug output
for (int i = 0; i < pa.size(); ++i)
{
QgsDebugMsgLevel("pa" + QString::number(pa.point(i).x()), 2);
QgsDebugMsgLevel("pa" + QString::number(pa.point(i).y()), 2);
}
#endif
// The default pen gives bevelled joins between segements of the
// polyline, which is good enough for the moment.
//preserve a copy of the pen before we start fiddling with it
QPen pen = p->pen(); // to be kept original
//
// experimental alpha transparency
// 255 = opaque
//
QPen myTransparentPen = p->pen(); // store current pen
QColor myColor = myTransparentPen.color();
//only set transparency from layer level if renderer does not provide
//transparency on class level
if(!mRenderer->usesTransparency())
{
myColor.setAlpha(mTransparencyLevel);
}
myTransparentPen.setColor(myColor);
p->setPen(myTransparentPen);
p->drawPolyline(pa);
// draw vertex markers if in editing mode, but only to the main canvas
if ( mEditable && drawingToEditingCanvas )
{
QgsVectorLayer::VertexMarkerType markerType = currentVertexMarkerType();
std::vector<double>::const_iterator xIt;
std::vector<double>::const_iterator yIt;
for(xIt = x.begin(), yIt = y.begin(); xIt != x.end(); ++xIt, ++yIt)
{
drawVertexMarker((int)(*xIt), (int)(*yIt), *p, markerType);
}
}
//restore the pen
p->setPen(pen);
return ptr;
}
unsigned char *QgsVectorLayer::drawPolygon(
unsigned char *feature,
QPainter *p,
const QgsMapToPixel *mtp,
const QgsCoordinateTransform *ct,
bool drawingToEditingCanvas)
{
typedef std::pair<std::vector<double>, std::vector<double> > ringType;
typedef ringType* ringTypePtr;
typedef std::vector<ringTypePtr> ringsType;
// get number of rings in the polygon
unsigned int numRings = *((int*)(feature + 1 + sizeof(int)));
if ( numRings == 0 ) // sanity check for zero rings in polygon
return feature + 9;
unsigned int wkbType = *((int*)(feature+1));
bool hasZValue = (wkbType == QGis::WKBPolygon25D);
int total_points = 0;
// A vector containing a pointer to a pair of double vectors.The
// first vector in the pair contains the x coordinates, and the
// second the y coordinates.
ringsType rings;
// Set pointer to the first ring
unsigned char* ptr = feature + 1 + 2 * sizeof(int);
for (register unsigned int idx = 0; idx < numRings; idx++)
{
unsigned int nPoints = *((int*)ptr);
ringTypePtr ring = new ringType(std::vector<double>(nPoints), std::vector<double>(nPoints));
ptr += 4;
// create a dummy vector for the z coordinate
std::vector<double> zVector(nPoints, 0.0);
// Extract the points from the WKB and store in a pair of
// vectors.
for (register unsigned int jdx = 0; jdx < nPoints; jdx++)
{
ring->first[jdx] = *((double *) ptr);
ptr += sizeof(double);
ring->second[jdx] = *((double *) ptr);
ptr += sizeof(double);
if (hasZValue)
ptr += sizeof(double);
}
// If ring has fewer than two points, what is it then?
// Anyway, this check prevents a crash
if (nPoints < 1)
{
QgsDebugMsg("Ring has only " + QString::number(nPoints) + " points! Skipping this ring.");
continue;
}
transformPoints(ring->first, ring->second, zVector, mtp, ct);
#if defined(Q_WS_X11)
// Work around a +/- 32768 limitation on coordinates in X11
// Look through the x and y coordinates and see if there are any
// that need trimming. If one is found, there's no need to look at
// the rest of them so end the loop at that point.
for (register unsigned int i = 0; i < nPoints; ++i)
{
if (std::abs(ring->first[i]) > QgsClipper::maxX ||
std::abs(ring->second[i]) > QgsClipper::maxY)
{
QgsClipper::trimFeature(ring->first, ring->second, false);
break;
}
}
#endif
// Don't bother keeping the ring if it has been trimmed out of
// existence.
if (ring->first.size() == 0)
delete ring;
else
{
rings.push_back(ring);
total_points += ring->first.size();
}
}
// Now we draw the polygons
// use painter paths for drawing polygons with holes
// when adding polygon to the path they invert the area
// this means that adding inner rings to the path creates
// holes in outer ring
QPainterPath path; // OddEven fill rule by default
// Only try to draw polygons if there is something to draw
if (total_points > 0)
{
// Store size here and use it in the loop to avoid penalty of
// multiple calls to size()
int numRings = rings.size();
for (register int i = 0; i < numRings; ++i)
{
// Store the pointer in a variable with a short name so as to make
// the following code easier to type and read.
ringTypePtr r = rings[i];
// only do this once to avoid penalty of additional calls
unsigned ringSize = r->first.size();
// Transfer points to the array of QPointF
QPolygonF pa(ringSize);
for (register unsigned int j = 0; j != ringSize; ++j)
{
pa[j].setX(r->first[j]);
pa[j].setY(r->second[j]);
}
path.addPolygon(pa);
// Tidy up the pointed to pairs of vectors as we finish with them
delete rings[i];
}
#ifdef QGISDEBUGVERBOSE
// this is only for verbose debug output -- no optimzation is
// needed :)
QgsDebugMsg("Pixel points are:");
for (int i = 0; i < pa.size(); ++i)
{
QgsDebugMsgLevel("i" + QString::number(i), 2);
QgsDebugMsgLevel("pa[i].x()" + QString::number(pa[i].x()), 2);
QgsDebugMsgLevel("pa[i].y()" + QString::number(pa[i].y()), 2);
}
std::cerr << "Ring positions are:\n";
QgsDebugMsg("Ring positions are:");
for (int i = 0; i < ringDetails.size(); ++i)
{
QgsDebugMsgLevel("ringDetails[i].first" + QString::number(ringDetails[i].first), 2);
QgsDebugMsgLevel("ringDetails[i].second" + QString::number(ringDetails[i].second), 2);
}
QgsDebugMsg("Outer ring point is " + QString::number(outerRingPt.x()) + ", " + QString::number(outerRingPt.y()));
#endif
/*
// A bit of code to aid in working out what values of
// QgsClipper::minX, etc cause the X11 zoom bug.
int largestX = -std::numeric_limits<int>::max();
int smallestX = std::numeric_limits<int>::max();
int largestY = -std::numeric_limits<int>::max();
int smallestY = std::numeric_limits<int>::max();
for (int i = 0; i < pa.size(); ++i)
{
largestX = std::max(largestX, pa.point(i).x());
smallestX = std::min(smallestX, pa.point(i).x());
largestY = std::max(largestY, pa.point(i).y());
smallestY = std::min(smallestY, pa.point(i).y());
}
std::cerr << "Largest X coordinate was " << largestX << '\n';
std::cerr << "Smallest X coordinate was " << smallestX << '\n';
std::cerr << "Largest Y coordinate was " << largestY << '\n';
std::cerr << "Smallest Y coordinate was " << smallestY << '\n';
*/
//preserve a copy of the brush and pen before we start fiddling with it
QBrush brush = p->brush(); //to be kept as original
QPen pen = p->pen(); // to be kept original
//
// experimental alpha transparency
// 255 = opaque
//
QBrush myTransparentBrush = p->brush();
QColor myColor = brush.color();
//only set transparency from layer level if renderer does not provide
//transparency on class level
if(!mRenderer->usesTransparency())
{
myColor.setAlpha(mTransparencyLevel);
}
myTransparentBrush.setColor(myColor);
QPen myTransparentPen = p->pen(); // store current pen
myColor = myTransparentPen.color();
//only set transparency from layer level if renderer does not provide
//transparency on class level
if(!mRenderer->usesTransparency())
{
myColor.setAlpha(mTransparencyLevel);
}
myTransparentPen.setColor(myColor);
p->setBrush(myTransparentBrush);
p->setPen (myTransparentPen);
//
// draw the polygon
//
p->drawPath(path);
// draw vertex markers if in editing mode, but only to the main canvas
if ( mEditable && drawingToEditingCanvas )
{
QgsVectorLayer::VertexMarkerType markerType = currentVertexMarkerType();
for(int i = 0; i < path.elementCount(); ++i)
{
const QPainterPath::Element & e = path.elementAt(i);
drawVertexMarker((int)e.x, (int)e.y, *p, markerType);
}
}
//
//restore brush and pen to original
//
p->setBrush ( brush );
p->setPen ( pen );
} // totalPoints > 0
return ptr;
}
bool QgsVectorLayer::draw(QgsRenderContext& renderContext)
{
//set update threshold before each draw to make sure the current setting is picked up
QSettings settings;
mUpdateThreshold = settings.value("Map/updateThreshold", 0).toInt();
//draw ( p, viewExtent, theMapToPixelTransform, ct, drawingToEditingCanvas, 1., 1.);
if (mRenderer)
{
// painter is active (begin has been called
/* Steps to draw the layer
1. get the features in the view extent by SQL query
2. read WKB for a feature
3. transform
4. draw
*/
QPen pen;
/*Pointer to a marker image*/
QImage marker;
/* Scale factor of the marker image*/
/* We set this to the symbolScale, and if it is NOT changed, */
/* we don't have to do another scaling here */
double markerScaleFactor = renderContext.rasterScaleFactor();
if(mEditable)
{
// Destroy all cached geometries and clear the references to them
deleteCachedGeometries();
}
updateFeatureCount();
int totalFeatures = pendingFeatureCount();
int featureCount = 0;
QgsFeature fet;
QgsAttributeList attributes = mRenderer->classificationAttributes();
select(attributes, renderContext.extent(), true);
try
{
while( getNextFeature(fet) )
{
if(renderContext.renderingStopped())
break;
#ifndef Q_WS_MAC //MH: disable this on Mac for now to avoid problems with resizing
if(mUpdateThreshold > 0 && 0 == featureCount % mUpdateThreshold)
{
emit screenUpdateRequested();
emit drawingProgress(featureCount, totalFeatures);
qApp->processEvents();
}
else if(featureCount % 1000 == 0)
{
emit drawingProgress(featureCount, totalFeatures);
qApp->processEvents();
}
#endif //Q_WS_MAC
if (mEditable)
{
// Cache this for the use of (e.g.) modifying the feature's uncommitted geometry.
mCachedGeometries[fet.featureId()] = *fet.geometry();
}
// check if feature is selected
// only show selections of the current layer
// TODO: create a mechanism to let layer know whether it's current layer or not [MD]
bool sel = mSelectedFeatureIds.contains(fet.featureId());
//QgsDebugMsg(QString("markerScale before renderFeature(): %1").arg(markerScaleFactor));
// markerScalerFactore reflects the wanted scaling of the marker
mRenderer->renderFeature(
renderContext.painter(),
fet,
&marker,
sel,
renderContext.scaleFactor(),
renderContext.rasterScaleFactor());
// markerScalerFactore now reflects the actual scaling of the marker that the render performed.
//QgsDebugMsg(QString("markerScale after renderFeature(): %1").arg(markerScaleFactor));
//double scale = renderContext.scaleFactor() / markerScaleFactor;
drawFeature(
renderContext.painter(),
fet,
&renderContext.mapToPixel(),
renderContext.coordTransform(),
&marker,
renderContext.scaleFactor(),
renderContext.rasterScaleFactor(),
renderContext.drawEditingInformation());
++featureCount;
}
}
catch (QgsCsException &cse)
{
QString msg("Failed to transform a point while drawing a feature of type '"
+ fet.typeName() + "'. Ignoring this feature.");
msg += cse.what();
QgsLogger::warning(msg);
}
}
else
{
QgsLogger::warning("QgsRenderer is null in QgsVectorLayer::draw()");
}
return TRUE; // Assume success always
}
void QgsVectorLayer::deleteCachedGeometries()
{
// Destroy any cached geometries
mCachedGeometries.clear();
}
void QgsVectorLayer::drawVertexMarker(int x, int y, QPainter& p, QgsVectorLayer::VertexMarkerType type)
{
if(type == QgsVectorLayer::SemiTransparentCircle)
{
p.setPen(QColor(50, 100, 120, 200));
p.setBrush(QColor(200, 200, 210, 120));
p.drawEllipse(QRectF(x - 7, y - 7, 14, 14));
}
else
{
int size = 15;
int m = (size-1)/2;
p.setPen(QColor(255, 0, 0));
p.drawLine(x-m, y+m, x+m, y-m);
p.drawLine(x-m, y-m, x+m, y+m);
}
}
void QgsVectorLayer::select(int number, bool emitSignal)
{
mSelectedFeatureIds.insert(number);
if (emitSignal)
{
emit selectionChanged();
}
}
void QgsVectorLayer::select(QgsRect & rect, bool lock)
{
// normalize the rectangle
rect.normalize();
if (lock == false)
{
removeSelection(FALSE); // don't emit signal
}
//select all the elements
select(QgsAttributeList(), rect, false);
QgsFeature f;
while( getNextFeature(f) )
{
select(f.featureId(), false); // don't emit signal (not to redraw it everytime)
}
emit selectionChanged(); // now emit signal to redraw layer
}
void QgsVectorLayer::invertSelection()
{
// copy the ids of selected features to tmp
QgsFeatureIds tmp = mSelectedFeatureIds;
removeSelection(FALSE); // don't emit signal
select(QgsAttributeList(), QgsRect(), true);
QgsFeature fet;
while ( getNextFeature(fet) )
{
select(fet.featureId(), false); // don't emit signal
}
for(QgsFeatureIds::iterator iter = tmp.begin(); iter != tmp.end(); ++iter)
{
mSelectedFeatureIds.remove(*iter);
}
emit selectionChanged();
}
void QgsVectorLayer::removeSelection(bool emitSignal)
{
mSelectedFeatureIds.clear();
if (emitSignal)
emit selectionChanged();
}
void QgsVectorLayer::triggerRepaint()
{
emit repaintRequested();
}
QgsVectorDataProvider* QgsVectorLayer::getDataProvider()
{
return mDataProvider;
}
const QgsVectorDataProvider* QgsVectorLayer::getDataProvider() const
{
return mDataProvider;
}
void QgsVectorLayer::setProviderEncoding(const QString& encoding)
{
if(mDataProvider)
{
mDataProvider->setEncoding(encoding);
}
}
const QgsRenderer* QgsVectorLayer::renderer() const
{
return mRenderer;
}
void QgsVectorLayer::setRenderer(QgsRenderer * r)
{
if (r != mRenderer)
{
delete mRenderer;
mRenderer = r;
}
}
QGis::VectorType QgsVectorLayer::vectorType() const
{
if (mDataProvider)
{
int type = mDataProvider->geometryType();
switch (type)
{
case QGis::WKBPoint:
case QGis::WKBPoint25D:
return QGis::Point;
case QGis::WKBLineString:
case QGis::WKBLineString25D:
return QGis::Line;
case QGis::WKBPolygon:
case QGis::WKBPolygon25D:
return QGis::Polygon;
case QGis::WKBMultiPoint:
case QGis::WKBMultiPoint25D:
return QGis::Point;
case QGis::WKBMultiLineString:
case QGis::WKBMultiLineString25D:
return QGis::Line;
case QGis::WKBMultiPolygon:
case QGis::WKBMultiPolygon25D:
return QGis::Polygon;
}
#ifdef QGISDEBUG
QgsLogger::debug("Warning: Data Provider Geometry type is not recognised, is", type, 1, __FILE__, __FUNCTION__, __LINE__);
#endif
}
else
{
#ifdef QGISDEBUG
qWarning("warning, pointer to mDataProvider is null in QgsVectorLayer::vectorType()");
#endif
}
// We shouldn't get here, and if we have, other things are likely to
// go wrong. Code that uses the vectorType() return value should be
// rewritten to cope with a value of QGis::Unknown. To make this
// need known, the following message is printed every time we get
// here.
std::cerr << "WARNING: This code (file " << __FILE__ << ", line "
<< __LINE__ << ") should never be reached. "
<< "Problems may occur...\n";
return QGis::Unknown;
}
QGis::WKBTYPE QgsVectorLayer::geometryType() const
{
return (QGis::WKBTYPE)(mGeometryType);
}
QgsRect QgsVectorLayer::boundingBoxOfSelected()
{
if(mSelectedFeatureIds.size()==0)//no selected features
{
return QgsRect(0,0,0,0);
}
QgsRect r, retval;
select(QgsAttributeList(), QgsRect(), true);
retval.setMinimal();
QgsFeature fet;
while ( getNextFeature(fet) )
{
if (mSelectedFeatureIds.contains(fet.featureId()))
{
if(fet.geometry())
{
r=fet.geometry()->boundingBox();
retval.combineExtentWith(&r);
}
}
}
if (retval.width() == 0.0 || retval.height() == 0.0)
{
// If all of the features are at the one point, buffer the
// rectangle a bit. If they are all at zero, do something a bit
// more crude.
if (retval.xMin() == 0.0 && retval.xMax() == 0.0 &&
retval.yMin() == 0.0 && retval.yMax() == 0.0)
{
retval.set(-1.0, -1.0, 1.0, 1.0);
}
else
{
const double padFactor = 1e-8;
double widthPad = retval.xMin() * padFactor;
double heightPad = retval.yMin() * padFactor;
double xmin = retval.xMin() - widthPad;
double xmax = retval.xMax() + widthPad;
double ymin = retval.yMin() - heightPad;
double ymax = retval.yMax() + heightPad;
retval.set(xmin, ymin, xmax, ymax);
}
}
return retval;
}
long QgsVectorLayer::featureCount() const
{
if ( !mDataProvider )
{
QgsLogger::warning(" QgsVectorLayer::featureCount() invoked with null mDataProvider");
return 0;
}
return mDataProvider->featureCount();
} // QgsVectorLayer::featureCount
long QgsVectorLayer::updateFeatureCount() const
{
if ( !mDataProvider )
{
QgsLogger::warning(" QgsVectorLayer::updateFeatureCount() invoked with null mDataProvider");
return 0;
}
return mDataProvider->updateFeatureCount();
}
void QgsVectorLayer::updateExtents()
{
mLayerExtent.setMinimal();
if(!mDataProvider)
QgsLogger::warning(" QgsVectorLayer::updateExtents() invoked with null mDataProvider");
if( mDeletedFeatureIds.isEmpty() )
{
// get the extent of the layer from the provider
// but only when there are some features already
if (mDataProvider->featureCount() != 0)
{
QgsRect r = mDataProvider->extent();
mLayerExtent.combineExtentWith(&r);
}
for(QgsFeatureList::iterator it=mAddedFeatures.begin(); it!=mAddedFeatures.end(); it++)
{
QgsRect r = it->geometry()->boundingBox();
mLayerExtent.combineExtentWith(&r);
}
return;
}
else
{
select(QgsAttributeList(), QgsRect(), true);
QgsFeature fet;
while( getNextFeature(fet) )
{
if (fet.geometry())
{
QgsRect bb = fet.geometry()->boundingBox();
mLayerExtent.combineExtentWith(&bb);
}
}
}
if (mLayerExtent.xMin() > mLayerExtent.xMax() && mLayerExtent.yMin() > mLayerExtent.yMax())
{
// special case when there are no features in provider nor any added
mLayerExtent = QgsRect(); // use rectangle with zero coordinates
}
// Send this (hopefully) up the chain to the map canvas
emit recalculateExtents();
}
QString QgsVectorLayer::subsetString()
{
if ( ! mDataProvider )
{
QgsLogger::warning(" QgsVectorLayer::subsetString() invoked with null mDataProvider");
return 0;
}
return mDataProvider->subsetString();
}
void QgsVectorLayer::setSubsetString(QString subset)
{
if ( ! mDataProvider )
{
QgsLogger::warning(" QgsVectorLayer::setSubsetString() invoked with null mDataProvider");
return;
}
mDataProvider->setSubsetString(subset);
// get the updated data source string from the provider
mDataSource = mDataProvider->dataSourceUri();
updateExtents();
}
void QgsVectorLayer::updateFeatureAttributes(QgsFeature &f)
{
if( mChangedAttributeValues.contains(f.featureId()) ) {
const QgsAttributeMap &map = mChangedAttributeValues[f.featureId()];
for(QgsAttributeMap::const_iterator it=map.begin(); it!=map.end(); it++)
f.changeAttribute(it.key(), it.value());
}
}
void QgsVectorLayer::updateFeatureGeometry(QgsFeature &f)
{
if( mChangedGeometries.contains(f.featureId()) )
f.setGeometry( mChangedGeometries[f.featureId()] );
}
void QgsVectorLayer::select(QgsAttributeList attributes, QgsRect rect, bool fetchGeometries)
{
if(!mDataProvider)
return;
mFetching = true;
mFetchRect = rect;
mFetchAttributes = attributes;
mFetchGeometry = fetchGeometries;
mFetchConsidered = mDeletedFeatureIds;
if( mEditable )
{
mFetchAddedFeaturesIt = mAddedFeatures.begin();
if( mFetchGeometry )
mFetchChangedGeomIt = mChangedGeometries.begin();
}
//look in the normal features of the provider
if( mFetchAttributes.size()>0 )
{
mDataProvider->select(mFetchAttributes, rect, fetchGeometries, true);
}
else
{
mDataProvider->select(QgsAttributeList(), rect, fetchGeometries, true);
}
}
bool QgsVectorLayer::getNextFeature(QgsFeature &f)
{
if(!mFetching)
return false;
if( mEditable )
{
if( !mFetchRect.isEmpty() )
{
// check if changed geometries are in rectangle
for(; mFetchChangedGeomIt!=mChangedGeometries.end(); mFetchChangedGeomIt++)
{
int fid = mFetchChangedGeomIt.key();
if( mFetchConsidered.contains( fid ) )
// skip deleted features
continue;
mFetchConsidered << fid;
if( !mFetchChangedGeomIt->intersects(mFetchRect) )
// skip changed geometries not in rectangle and don't check again
continue;
f.setFeatureId( fid );
if(mFetchGeometry)
f.setGeometry( mFetchChangedGeomIt.value() );
if( mFetchAttributes.size()>0 )
{
if( fid<0 )
{
// fid<0 => in mAddedFeatures
bool found = false;
for (QgsFeatureList::iterator it=mAddedFeatures.begin(); it!=mAddedFeatures.end(); it++)
{
if( fid!=it->featureId() )
{
found = true;
f.setAttributeMap( it->attributeMap() );
break;
}
}
if(!found)
QgsLogger::warning( QString("No attributes for the added feature %1 found").arg(f.featureId()) );
}
else
{
// retrieve attributes from provider
QgsFeature tmp;
mDataProvider->getFeatureAtId(fid, tmp, false, mDataProvider->allAttributesList());
updateFeatureAttributes(tmp);
f.setAttributeMap( tmp.attributeMap() );
}
}
// return complete feature
mFetchChangedGeomIt++;
return true;
}
// no more changed geometries
}
for (; mFetchAddedFeaturesIt != mAddedFeatures.end(); mFetchAddedFeaturesIt++)
{
int fid = mFetchAddedFeaturesIt->featureId();
if( mFetchConsidered.contains(fid) )
// must have changed geometry outside rectangle
continue;
if( !mFetchRect.isEmpty() &&
mFetchAddedFeaturesIt->geometry() &&
!mFetchAddedFeaturesIt->geometry()->intersects( mFetchRect ) )
// skip added features not in rectangle
continue;
f.setFeatureId(fid);
if(mFetchGeometry)
f.setGeometry( *mFetchAddedFeaturesIt->geometry() );
if( mFetchAttributes.size()>0 )
{
f.setAttributeMap(mFetchAddedFeaturesIt->attributeMap());
updateFeatureAttributes(f);
}
mFetchAddedFeaturesIt++;
return true;
}
// no more added features
}
while( getDataProvider()->getNextFeature(f) )
{
if( mFetchConsidered.contains( f.featureId() ) )
continue;
if( mEditable )
updateFeatureAttributes(f);
// found it
return true;
}
mFetching = false;
return false;
}
int QgsVectorLayer::getFeatureAtId(int featureId, QgsFeature& f, bool fetchGeometries, bool fetchAttributes)
{
if(!mDataProvider)
return 1;
if(mDeletedFeatureIds.contains(featureId))
return 2;
if( fetchGeometries && mChangedGeometries.contains(featureId) )
{
f.setFeatureId(featureId);
f.setGeometry( mChangedGeometries[featureId] );
if(fetchAttributes)
{
if( featureId<0 )
{
// featureId<0 => in mAddedFeatures
bool found = false;
for (QgsFeatureList::iterator it=mAddedFeatures.begin(); it!=mAddedFeatures.end(); it++)
{
if( featureId!=it->featureId() )
{
found = true;
f.setAttributeMap( it->attributeMap() );
break;
}
}
if(!found)
QgsLogger::warning( QString("No attributes for the added feature %1 found").arg(f.featureId()) );
}
else
{
// retrieve attributes from provider
QgsFeature tmp;
mDataProvider->getFeatureAtId(featureId, tmp, false, mDataProvider->allAttributesList());
updateFeatureAttributes(tmp);
f.setAttributeMap( tmp.attributeMap() );
}
updateFeatureAttributes(f);
}
}
//added features
for(QgsFeatureList::iterator iter = mAddedFeatures.begin(); iter!=mAddedFeatures.end(); ++iter)
{
if(iter->featureId() == featureId)
{
f.setFeatureId(iter->featureId());
if(fetchGeometries)
f.setGeometry( *iter->geometry() );
if(fetchAttributes)
f.setAttributeMap(iter->attributeMap());
return 0;
}
}
// regular features
if(fetchAttributes)
{
if(mDataProvider->getFeatureAtId(featureId, f, fetchGeometries, mDataProvider->allAttributesList()))
{
updateFeatureAttributes(f);
return 0;
}
}
else
{
if(mDataProvider->getFeatureAtId(featureId, f, fetchGeometries, QgsAttributeList()))
{
return 0;
}
}
return 3;
}
bool QgsVectorLayer::addFeature(QgsFeature& f, bool alsoUpdateExtent)
{
static int addedIdLowWaterMark = -1;
if (!mDataProvider)
{
return false;
}
if(!(mDataProvider->capabilities() & QgsVectorDataProvider::AddFeatures))
{
return false;
}
if(!isEditable())
{
return false;
}
//assign a temporary id to the feature (use negative numbers)
addedIdLowWaterMark--;
QgsDebugMsg("Assigned feature id " + QString::number(addedIdLowWaterMark));
// Force a feature ID (to keep other functions in QGIS happy,
// providers will use their own new feature ID when we commit the new feature)
// and add to the known added features.
f.setFeatureId(addedIdLowWaterMark);
mAddedFeatures.append(f);
setModified(true);
if (alsoUpdateExtent)
{
updateExtents();
}
return true;
}
bool QgsVectorLayer::insertVertexBefore(double x, double y, int atFeatureId, int beforeVertex)
{
if (!mEditable)
{
return false;
}
if (mDataProvider)
{
if (!mChangedGeometries.contains(atFeatureId))
{
// first time this geometry has changed since last commit
if(!mCachedGeometries.contains(atFeatureId))
{
return false;
}
mChangedGeometries[atFeatureId] = mCachedGeometries[atFeatureId];
}
mChangedGeometries[atFeatureId].insertVertexBefore(x, y, beforeVertex);
setModified(true, true); // only geometry was changed
return true;
}
return false;
}
bool QgsVectorLayer::moveVertexAt(double x, double y, int atFeatureId, int atVertex)
{
if (!mEditable)
{
return false;
}
if (mDataProvider)
{
if (!mChangedGeometries.contains(atFeatureId))
{
// first time this geometry has changed since last commit
if(!mCachedGeometries.contains(atFeatureId))
{
return false;
}
mChangedGeometries[atFeatureId] = mCachedGeometries[atFeatureId];
}
mChangedGeometries[atFeatureId].moveVertexAt(x, y, atVertex);
setModified(true, true); // only geometry was changed
return true;
}
return false;
}
bool QgsVectorLayer::deleteVertexAt(int atFeatureId, int atVertex)
{
if (!mEditable)
{
return false;
}
if (mDataProvider)
{
if (!mChangedGeometries.contains(atFeatureId))
{
// first time this geometry has changed since last commit
if(!mCachedGeometries.contains(atFeatureId))
{
return false;
}
mChangedGeometries[atFeatureId] = mCachedGeometries[atFeatureId];
}
mChangedGeometries[atFeatureId].deleteVertexAt(atVertex);
setModified(true, true); // only geometry was changed
return true;
}
return false;
}
bool QgsVectorLayer::deleteSelectedFeatures()
{
if(!(mDataProvider->capabilities() & QgsVectorDataProvider::DeleteFeatures))
{
return false;
}
if(!isEditable())
{
return false;
}
if( mSelectedFeatureIds.size()==0 )
return true;
while( mSelectedFeatureIds.size()>0 )
{
int fid = *mSelectedFeatureIds.begin();
deleteFeature( fid ); // removes from selection
}
emit selectionChanged();
triggerRepaint();
updateExtents();
return true;
}
int QgsVectorLayer::addRing(const QList<QgsPoint>& ring)
{
int addRingReturnCode = 5; //default: return code for 'ring not inserted'
double xMin, yMin, xMax, yMax;
QgsRect bBox;
if(boundingBoxFromPointList(ring, xMin, yMin, xMax, yMax) == 0)
{
bBox.setXmin(xMin); bBox.setYmin(yMin); bBox.setXmax(xMax); bBox.setYmax(yMax);
}
else
{
return 3; //ring not valid
}
select(QgsAttributeList(), bBox, true);
QgsFeature f;
while( getNextFeature(f) )
{
addRingReturnCode = f.geometry()->addRing(ring);
if(addRingReturnCode == 0)
{
mChangedGeometries.insert(f.featureId(), *f.geometry());
setModified(true, true);
break;
}
}
return addRingReturnCode;
}
int QgsVectorLayer::addIsland(const QList<QgsPoint>& ring)
{
//number of selected features must be 1
if(mSelectedFeatureIds.size() < 1)
{
QgsDebugMsg("Number of selected features <1");
return 4;
}
else if(mSelectedFeatureIds.size() > 1)
{
QgsDebugMsg("Number of selected features >1");
return 5;
}
int selectedFeatureId = *mSelectedFeatureIds.constBegin();
//look if geometry of selected feature already contains geometry changes
QgsGeometryMap::iterator changedIt = mChangedGeometries.find(selectedFeatureId);
if(changedIt != mChangedGeometries.end())
{
return changedIt->addIsland(ring);
}
//look if id of selected feature belongs to an added feature
for(QgsFeatureList::iterator addedIt = mAddedFeatures.begin(); addedIt != mAddedFeatures.end(); ++addedIt)
{
if(addedIt->featureId() == selectedFeatureId)
{
return addedIt->geometry()->addIsland(ring);
}
}
//else, if must be contained in mCachedGeometries
QgsGeometryMap::iterator cachedIt = mCachedGeometries.find(selectedFeatureId);
if(cachedIt != mCachedGeometries.end())
{
int errorCode = cachedIt->addIsland(ring);
if(errorCode == 0)
{
mChangedGeometries.insert(selectedFeatureId, *cachedIt);
setModified(true, true);
}
return errorCode;
}
return 6; //geometry not found
}
int QgsVectorLayer::translateFeature(int featureId, double dx, double dy)
{
//look if geometry of selected feature already contains geometry changes
QgsGeometryMap::iterator changedIt = mChangedGeometries.find(featureId);
if(changedIt != mChangedGeometries.end())
{
return changedIt->translate(dx, dy);
}
//look if id of selected feature belongs to an added feature
for(QgsFeatureList::iterator addedIt = mAddedFeatures.begin(); addedIt != mAddedFeatures.end(); ++addedIt)
{
if(addedIt->featureId() == featureId)
{
return addedIt->geometry()->translate(dx, dy);
}
}
//else, if must be contained in mCachedGeometries
QgsGeometryMap::iterator cachedIt = mCachedGeometries.find(featureId);
if(cachedIt != mCachedGeometries.end())
{
int errorCode = cachedIt->translate(dx, dy);
if(errorCode == 0)
{
mChangedGeometries.insert(featureId, *cachedIt);
setModified(true, true);
}
return errorCode;
}
return 1; //geometry not found
}
int QgsVectorLayer::splitFeatures(const QList<QgsPoint>& splitLine, bool topologicalEditing)
{
QgsGeometry* newGeometry = 0;
QgsFeatureList newFeatures; //store all the newly created features
double xMin, yMin, xMax, yMax;
QgsRect bBox; //bounding box of the split line
int returnCode = 0;
int splitFunctionReturn; //return code of QgsGeometry::splitGeometry
QgsFeatureList featureList;
const QgsFeatureIds selectedIds = selectedFeaturesIds();
if(selectedIds.size() > 0)//consider only the selected features if there is a selection
{
featureList = selectedFeatures();
}
else //else consider all the feature that intersect the bounding box of the split line
{
if(boundingBoxFromPointList(splitLine, xMin, yMin, xMax, yMax) == 0)
{
bBox.setXmin(xMin); bBox.setYmin(yMin); bBox.setXmax(xMax); bBox.setYmax(yMax);
}
else
{
return 1;
}
if(bBox.isEmpty())
{
//if the bbox is a line, try to make a square out of it
if(bBox.width()==0.0 && bBox.height() > 0)
{
bBox.setXmin(bBox.xMin() - bBox.height()/2);
bBox.setXmax(bBox.xMax() + bBox.height()/2);
}
else if(bBox.height()==0.0 && bBox.width()>0)
{
bBox.setYmin(bBox.yMin() - bBox.width()/2);
bBox.setYmax(bBox.yMax() + bBox.width()/2);
}
else
{
return 2;
}
}
select(QgsAttributeList(), bBox, true);
QgsFeature f;
while( getNextFeature(f) )
featureList << f;
}
QgsFeatureList::iterator select_it = featureList.begin();
for(; select_it != featureList.end(); ++select_it)
{
QList<QgsGeometry*> newGeometries;
QgsGeometry* newGeometry = 0;
splitFunctionReturn = select_it->geometry()->splitGeometry(splitLine, newGeometries);
if(splitFunctionReturn == 0)
{
//change this geometry
mChangedGeometries.insert(select_it->featureId(), *(select_it->geometry()));
//insert new features
for(int i = 0; i < newGeometries.size(); ++i)
{
newGeometry = newGeometries.at(i);
QgsFeature newFeature;
newFeature.setGeometry(newGeometry);
newFeature.setAttributeMap(select_it->attributeMap());
newFeatures.append(newFeature);
if(topologicalEditing) //add topological points for new feature
{
addTopologicalPoints(newGeometry);
}
}
setModified(true, true);
//add topological points for this geometry if necessary
if(topologicalEditing)
{
addTopologicalPoints(select_it->geometry());
}
}
else if(splitFunctionReturn > 1) //1 means no split but also no error
{
returnCode = 3;
}
}
//now add the new features to this vectorlayer
addFeatures(newFeatures, false);
return returnCode;
}
int QgsVectorLayer::removePolygonIntersections(QgsGeometry* geom)
{
int returnValue = 0;
//first test if geom really has type polygon or multipolygon
if(geom->vectorType() != QGis::Polygon)
{
return 1;
}
//get bounding box of geom
QgsRect geomBBox = geom->boundingBox();
//get list of features that intersect this bounding box
select(QgsAttributeList(), geomBBox, true);
QgsFeature f;
while( getNextFeature(f) )
{
//call geometry->makeDifference for each feature
QgsGeometry *currentGeom = f.geometry();
if(currentGeom)
{
if( geom->makeDifference(currentGeom)!=0 )
{
returnValue = 2;
}
}
}
return returnValue;
}
int QgsVectorLayer::addTopologicalPoints(QgsGeometry* geom)
{
if(!geom)
{
return 1;
}
int returnVal = 0;
QGis::WKBTYPE wkbType = geom->wkbType();
switch(wkbType)
{
//line
case QGis::WKBLineString25D:
case QGis::WKBLineString:
{
QgsPolyline theLine = geom->asPolyline();
QgsPolyline::const_iterator line_it = theLine.constBegin();
for(; line_it != theLine.constEnd(); ++line_it)
{
if(addTopologicalPoints(*line_it) != 0)
{
returnVal = 2;
}
}
break;
}
//multiline
case QGis::WKBMultiLineString25D:
case QGis::WKBMultiLineString:
{
QgsMultiPolyline theMultiLine = geom->asMultiPolyline();
QgsPolyline currentPolyline;
for(int i = 0; i < theMultiLine.size(); ++i)
{
QgsPolyline::const_iterator line_it = currentPolyline.constBegin();
for(; line_it != currentPolyline.constEnd(); ++line_it)
{
if(addTopologicalPoints(*line_it) != 0)
{
returnVal = 2;
}
}
}
break;
}
//polygon
case QGis::WKBPolygon25D:
case QGis::WKBPolygon:
{
QgsPolygon thePolygon = geom->asPolygon();
QgsPolyline currentRing;
for(int i = 0; i < thePolygon.size(); ++i)
{
currentRing = thePolygon.at(i);
QgsPolyline::const_iterator line_it = currentRing.constBegin();
for(; line_it != currentRing.constEnd(); ++line_it)
{
if(addTopologicalPoints(*line_it) != 0)
{
returnVal = 2;
}
}
}
break;
}
//multipolygon
case QGis::WKBMultiPolygon25D:
case QGis::WKBMultiPolygon:
{
QgsMultiPolygon theMultiPolygon = geom->asMultiPolygon();
QgsPolygon currentPolygon;
QgsPolyline currentRing;
for(int i = 0; i < theMultiPolygon.size(); ++i)
{
currentPolygon = theMultiPolygon.at(i);
for(int j = 0; j < currentPolygon.size(); ++j)
{
currentRing = currentPolygon.at(j);
QgsPolyline::const_iterator line_it = currentRing.constBegin();
for(; line_it != currentRing.constEnd(); ++line_it)
{
if(addTopologicalPoints(*line_it) != 0)
{
returnVal = 2;
}
}
}
}
break;
}
}
return returnVal;
}
int QgsVectorLayer::addTopologicalPoints(const QgsPoint& p)
{
QMultiMap<double, QgsSnappingResult> snapResults; //results from the snapper object
//we also need to snap to vertex to make sure the vertex does not already exist in this geometry
QMultiMap<double, QgsSnappingResult> vertexSnapResults;
QList<QgsSnappingResult> filteredSnapResults; //we filter out the results that are on existing vertices
const double threshold = 0.00000001;
if(snapWithContext(p, threshold, snapResults, QgsSnapper::SNAP_TO_SEGMENT) != 0)
{
return 2;
}
QMultiMap<double, QgsSnappingResult>::const_iterator snap_it = snapResults.constBegin();
QMultiMap<double, QgsSnappingResult>::const_iterator vertex_snap_it;
for(; snap_it != snapResults.constEnd(); ++snap_it)
{
//test if p is already a vertex of this geometry. If yes, don't insert it
bool vertexAlreadyExists = false;
if(snapWithContext(p, threshold, vertexSnapResults, QgsSnapper::SNAP_TO_VERTEX) != 0)
{
continue;
}
vertex_snap_it = vertexSnapResults.constBegin();
for(; vertex_snap_it != vertexSnapResults.constEnd(); ++vertex_snap_it)
{
if(snap_it.value().snappedAtGeometry == vertex_snap_it.value().snappedAtGeometry)
{
vertexAlreadyExists = true;
}
}
if(!vertexAlreadyExists)
{
filteredSnapResults.push_back(*snap_it);
}
}
insertSegmentVerticesForSnap(filteredSnapResults);
return 0;
}
QgsLabel * QgsVectorLayer::label()
{
return mLabel;
}
void QgsVectorLayer::setLabelOn ( bool on )
{
mLabelOn = on;
}
bool QgsVectorLayer::labelOn ( void )
{
return mLabelOn;
}
bool QgsVectorLayer::startEditing()
{
if (!mDataProvider)
{
return false;
}
if( !(mDataProvider->capabilities() & QgsVectorDataProvider::AddFeatures) )
{
return false;
}
if(mEditable)
{
// editing already underway
return false;
}
mEditable=true;
mUpdatedFields = mDataProvider->fields();
mMaxUpdatedIndex = -1;
for(QgsFieldMap::const_iterator it=mUpdatedFields.begin(); it!=mUpdatedFields.end(); it++)
if( it.key() > mMaxUpdatedIndex )
mMaxUpdatedIndex = it.key();
emit editingStarted();
return true;
}
bool QgsVectorLayer::readXml( QDomNode & layer_node )
{
QgsDebugMsg(QString("Datasource in QgsVectorLayer::readXml: ") + mDataSource.toLocal8Bit().data());
// process the attribute actions
mActions->readXML(layer_node);
//process provider key
QDomNode pkeyNode = layer_node.namedItem("provider");
if (pkeyNode.isNull())
{
mProviderKey = "";
}
else
{
QDomElement pkeyElt = pkeyNode.toElement();
mProviderKey = pkeyElt.text();
}
// determine type of vector layer
if ( ! mProviderKey.isNull() )
{
// if the provider string isn't empty, then we successfully
// got the stored provider
}
else if ( mDataSource.contains("dbname=") )
{
mProviderKey = "postgres";
}
else
{
mProviderKey = "ogr";
}
if ( ! setDataProvider( mProviderKey ) )
{
return false;
}
//read provider encoding
QDomNode encodingNode = layer_node.namedItem("encoding");
if( ! encodingNode.isNull() && mDataProvider )
{
mDataProvider->setEncoding(encodingNode.toElement().text());
}
// get and set the display field if it exists.
QDomNode displayFieldNode = layer_node.namedItem("displayfield");
if (!displayFieldNode.isNull())
{
QDomElement e = displayFieldNode.toElement();
setDisplayField(e.text());
}
QDomNode editTypesNode = layer_node.namedItem("edittypes");
if( !editTypesNode.isNull() )
{
QDomNodeList editTypeNodes = editTypesNode.childNodes();
for(int i=0; i<editTypeNodes.size(); i++)
{
QDomNode editTypeNode = editTypeNodes.at(i);
QDomElement editTypeElement = editTypeNode.toElement();
QString name = editTypeElement.attribute("name");
EditType editType = (EditType) editTypeElement.attribute("type").toInt();
mEditTypes.insert( name, editType );
if( editType==ValueMap && editTypeNode.hasChildNodes() )
{
mValueMaps.insert( name, QMap<QString,QVariant>() );
QDomNodeList valueMapNodes = editTypeNode.childNodes();
for(int j=0; j<valueMapNodes.size(); j++)
{
QDomElement value = valueMapNodes.at(j).toElement();
mValueMaps[ name ].insert( value.attribute("key"), value.attribute("value") );
}
} else if( editType==Range )
{
QVariant min = editTypeElement.attribute("min");
QVariant max = editTypeElement.attribute("max");
QVariant step = editTypeElement.attribute("step");
mRanges[ name ] = RangeData(min,max,step);
}
}
}
// create and bind a renderer to this layer
QDomNode singlenode = layer_node.namedItem("singlesymbol");
QDomNode graduatednode = layer_node.namedItem("graduatedsymbol");
QDomNode continuousnode = layer_node.namedItem("continuoussymbol");
QDomNode singlemarkernode = layer_node.namedItem("singlemarker");
QDomNode graduatedmarkernode = layer_node.namedItem("graduatedmarker");
QDomNode uniquevaluenode = layer_node.namedItem("uniquevalue");
QDomNode labelnode = layer_node.namedItem("label");
QDomNode uniquemarkernode = layer_node.namedItem("uniquevaluemarker");
//std::auto_ptr<QgsRenderer> renderer; actually the renderer SHOULD NOT be
//deleted when this function finishes, otherwise the application will
//crash
// XXX this seems to be a dangerous implementation; should re-visit design
QgsRenderer * renderer;
// XXX Kludge!
// if we don't have a coordinate transform, get one
//
// Im commenting this out - if the layer was serialied in a
// >=0.7 project it should have been validated and have all
// coord xform info
//
//if ( ! coordinateTransform() )
//{
// setCoordinateSystem();
//}
if (!singlenode.isNull())
{
renderer = new QgsSingleSymbolRenderer(vectorType());
renderer->readXML(singlenode, *this);
}
else if (!graduatednode.isNull())
{
renderer = new QgsGraduatedSymbolRenderer(vectorType());
renderer->readXML(graduatednode, *this);
}
else if (!continuousnode.isNull())
{
renderer = new QgsContinuousColorRenderer(vectorType());
renderer->readXML(continuousnode, *this);
}
else if (!uniquevaluenode.isNull())
{
renderer = new QgsUniqueValueRenderer(vectorType());
renderer->readXML(uniquevaluenode, *this);
}
// Test if labeling is on or off
QDomElement element = labelnode.toElement();
int labelOn = element.text().toInt();
if (labelOn < 1)
{
setLabelOn(false);
}
else
{
setLabelOn(true);
}
QgsDebugMsg("Testing if qgsvectorlayer can call label readXML routine")
QDomNode labelattributesnode = layer_node.namedItem("labelattributes");
if(!labelattributesnode.isNull())
{
QgsDebugMsg("qgsvectorlayer calling label readXML routine")
mLabel->readXML(labelattributesnode);
}
return mValid; // should be true if read successfully
} // void QgsVectorLayer::readXml
bool QgsVectorLayer::setDataProvider( QString const & provider )
{
// XXX should I check for and possibly delete any pre-existing providers?
// XXX How often will that scenario occur?
mProviderKey = provider; // XXX is this necessary? Usually already set
// XXX when execution gets here.
//XXX - This was a dynamic cast but that kills the Windows
// version big-time with an abnormal termination error
mDataProvider =
(QgsVectorDataProvider*)(QgsProviderRegistry::instance()->getProvider(provider,mDataSource));
if (mDataProvider)
{
QgsDebugMsg( "Instantiated the data provider plugin" );
mValid = mDataProvider->isValid();
if (mValid)
{
// TODO: Check if the provider has the capability to send fullExtentCalculated
connect(mDataProvider, SIGNAL( fullExtentCalculated() ), this, SLOT(updateExtents()) );
// get the extent
QgsRect mbr = mDataProvider->extent();
// show the extent
QString s = mbr.stringRep();
QgsDebugMsg("Extent of layer: " + s);
// store the extent
mLayerExtent.setXmax(mbr.xMax());
mLayerExtent.setXmin(mbr.xMin());
mLayerExtent.setYmax(mbr.yMax());
mLayerExtent.setYmin(mbr.yMin());
// get and store the feature type
mGeometryType = mDataProvider->geometryType();
// look at the fields in the layer and set the primary
// display field using some real fuzzy logic
setDisplayField();
if (mProviderKey == "postgres")
{
QgsDebugMsg("Beautifying layer name " + name());
// adjust the display name for postgres layers
QRegExp reg("\"[^\"]+\"\\.\"([^\"]+)\"");
reg.indexIn(name());
QStringList stuff = reg.capturedTexts();
QString lName = stuff[1];
if (lName.length() == 0) // fallback
lName = name();
setLayerName(lName);
QgsDebugMsg("Beautifying layer name " + name());
// deal with unnecessary schema qualification to make v.in.ogr happy
mDataSource = mDataProvider->dataSourceUri();
}
// label
mLabel = new QgsLabel ( mDataProvider->fields() );
mLabelOn = false;
}
else
{
QgsDebugMsg("Invalid provider plugin " + QString(mDataSource.toUtf8()));
return false;
}
}
else
{
QgsDebugMsg( " unable to get data provider" );
return false;
}
return true;
} // QgsVectorLayer:: setDataProvider
/* virtual */ bool QgsVectorLayer::writeXml( QDomNode & layer_node,
QDomDocument & document )
{
// first get the layer element so that we can append the type attribute
QDomElement mapLayerNode = layer_node.toElement();
if ( mapLayerNode.isNull() || ("maplayer" != mapLayerNode.nodeName()) )
{
qDebug( "QgsVectorLayer::writeXML() can't find <maplayer>" );
return false;
}
mapLayerNode.setAttribute( "type", "vector" );
// set the geometry type
mapLayerNode.setAttribute( "geometry", QGis::qgisVectorGeometryType[vectorType()]);
// add provider node
QDomElement provider = document.createElement( "provider" );
QDomText providerText = document.createTextNode( providerType() );
provider.appendChild( providerText );
layer_node.appendChild( provider );
//provider encoding
QDomElement encoding = document.createElement("encoding");
QDomText encodingText = document.createTextNode(mDataProvider->encoding());
encoding.appendChild( encodingText );
layer_node.appendChild( encoding );
//classification field(s)
QgsAttributeList attributes=mRenderer->classificationAttributes();
const QgsFieldMap providerFields = mDataProvider->fields();
for(QgsAttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it)
{
QDomElement classificationElement = document.createElement("classificationattribute");
QDomText classificationText = document.createTextNode(providerFields[*it].name());
classificationElement.appendChild(classificationText);
layer_node.appendChild(classificationElement);
}
if( mEditTypes.size()>0 )
{
QDomElement editTypesElement = document.createElement("edittypes");
for(QMap<QString,EditType>::const_iterator it = mEditTypes.begin(); it != mEditTypes.end(); ++it)
{
QDomElement editTypeElement = document.createElement("edittype");
editTypeElement.setAttribute( "name", it.key() );
editTypeElement.setAttribute( "type", it.value() );
if( it.value() == ValueMap )
{
if( mValueMaps.contains( it.key() ) )
{
const QMap<QString,QVariant> &map = mValueMaps[ it.key() ];
for(QMap<QString,QVariant>::const_iterator vmit=map.begin(); vmit!=map.end(); vmit++)
{
QDomElement value = document.createElement("valuepair");
value.setAttribute( "key", vmit.key() );
value.setAttribute( "value", vmit.value().toString() );
editTypeElement.appendChild( value );
}
}
} else if( it.value() == Range ) {
if( mRanges.contains( it.key() ) )
{
editTypeElement.setAttribute( "min", mRanges[ it.key() ].mMin.toString() );
editTypeElement.setAttribute( "max", mRanges[ it.key() ].mMax.toString() );
editTypeElement.setAttribute( "step", mRanges[ it.key() ].mStep.toString() );
}
}
editTypesElement.appendChild(editTypeElement);
}
layer_node.appendChild(editTypesElement);
}
// add the display field
QDomElement dField = document.createElement( "displayfield" );
QDomText dFieldText = document.createTextNode( displayField() );
dField.appendChild( dFieldText );
layer_node.appendChild( dField );
// add label node
QDomElement label = document.createElement( "label" );
QDomText labelText = document.createTextNode( "" );
if ( labelOn() )
{
labelText.setData( "1" );
}
else
{
labelText.setData( "0" );
}
label.appendChild( labelText );
layer_node.appendChild( label );
// add attribute actions
mActions->writeXML(layer_node, document);
// renderer specific settings
const QgsRenderer * myRenderer = renderer();
if( myRenderer )
{
myRenderer->writeXML(layer_node, document);
}
else
{
std::cerr << __FILE__ << ":" << __LINE__
<< " no renderer\n";
// XXX return false?
}
// Now we get to do all that all over again for QgsLabel
// XXX Since this is largely a cut-n-paste from the previous, this
// XXX therefore becomes a candidate to be generalized into a separate
// XXX function. I think.
QgsLabel * myLabel = this->label();
if ( myLabel )
{
QString fieldname = myLabel->labelField(QgsLabel::Text);
if(fieldname!="") {
dField = document.createElement( "labelfield" );
dFieldText = document.createTextNode( fieldname );
dField.appendChild( dFieldText );
layer_node.appendChild( dField );
}
std::stringstream labelXML;
myLabel->writeXML(labelXML);
QDomDocument labelDOM;
std::string rawXML;
std::string temp_str;
QString errorMsg;
int errorLine;
int errorColumn;
// start with bogus XML header
rawXML = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
temp_str = labelXML.str();
rawXML += temp_str;
#ifdef QGISDEBUG
std::cout << rawXML << std::endl << std::flush;
#endif
const char * s = rawXML.c_str(); // debugger probe
// Use the const char * form of the xml to make non-stl qt happy
if ( ! labelDOM.setContent( QString::fromUtf8(s), &errorMsg, &errorLine, &errorColumn ) )
{
qDebug( ("XML import error at line %d column %d " + errorMsg).toLocal8Bit().data(), errorLine, errorColumn );
return false;
}
// lastChild() because the first two nodes are the <xml> and
// <!DOCTYPE> nodes; the label node follows that, and is (hopefully)
// the last node.
QDomNode labelDOMNode = document.importNode( labelDOM.lastChild(), true );
if ( ! labelDOMNode.isNull() )
{
layer_node.appendChild( labelDOMNode );
}
else
{
qDebug( "not able to import label DOM node" );
// XXX return false?
}
}
return true;
} // bool QgsVectorLayer::writeXml
bool QgsVectorLayer::changeAttributeValue(int fid, int field, QVariant value, bool emitSignal)
{
if( !isEditable() )
return false;
if(fid>=0)
{
// changed attribute of existing feature
if( !mChangedAttributeValues.contains(fid) )
{
mChangedAttributeValues.insert( fid, QgsAttributeMap() );
}
mChangedAttributeValues[fid].insert(field, value);
}
else
{
// updated added feature
int i;
for(i=0; i<mAddedFeatures.size() && mAddedFeatures[i].featureId()!=fid; i++)
;
if(i==mAddedFeatures.size())
return false;
mAddedFeatures[i].changeAttribute(field, value);
}
setModified(true, false);
if(emitSignal)
emit attributeValueChanged(fid, field, value);
return true;
}
bool QgsVectorLayer::addAttribute( QString name, QString type )
{
if ( !isEditable() )
return false;
for(QgsFieldMap::const_iterator it=mUpdatedFields.begin(); it!=mUpdatedFields.end(); it++)
{
if( it.value().name() == name )
return false;
}
const QgsNativeTypeMap &types = mDataProvider->supportedNativeTypes();
QVariant::Type typeType = QVariant::String;
if( types.contains(type) )
typeType = (QVariant::Type) types[type];
mMaxUpdatedIndex++;
mUpdatedFields.insert( mMaxUpdatedIndex, QgsField(name, typeType, type) );
mAddedAttributeIds.insert( mMaxUpdatedIndex );
setModified(true, false);
emit attributeAdded(mMaxUpdatedIndex);
return true;
}
bool QgsVectorLayer::deleteAttribute(int index)
{
if ( !isEditable() )
return false;
if ( mDeletedAttributeIds.contains( index ) )
return false;
if ( !mDataProvider->fields().contains(index) )
return false;
mDeletedAttributeIds.insert(index);
setModified(true, false);
emit attributeDeleted(index);
return true;
}
bool QgsVectorLayer::deleteFeature(int fid)
{
if ( !isEditable() )
return false;
if ( mDeletedFeatureIds.contains( fid ) )
return true;
mSelectedFeatureIds.remove(fid); // remove it from selection
mDeletedFeatureIds.insert(fid);
setModified(true, false);
emit featureDeleted(fid);
return true;
}
const QgsFieldMap &QgsVectorLayer::pendingFields()
{
return isEditable() ? mUpdatedFields : mDataProvider->fields();
}
QgsAttributeList QgsVectorLayer::pendingAllAttributesList()
{
return isEditable() ? mUpdatedFields.keys() : mDataProvider->allAttributesList();
}
int QgsVectorLayer::pendingFeatureCount()
{
return mDataProvider->featureCount()
+ mAddedFeatures.size()
- mDeletedFeatureIds.size();
}
bool QgsVectorLayer::commitChanges()
{
bool success = true;
mCommitErrors.clear();
if ( !mDataProvider )
{
mCommitErrors << tr("ERROR: no provider");
return false;
}
if ( !isEditable() )
{
mCommitErrors << tr("ERROR: layer not editable");
return false;
}
int cap = mDataProvider->capabilities();
//
// add attributes
//
bool attributesChanged = false;
if ( mAddedAttributeIds.size()>0 )
{
QgsNewAttributesMap addedAttributes;
for(QgsAttributeIds::const_iterator it = mAddedAttributeIds.begin(); it!=mAddedAttributeIds.end(); it++)
addedAttributes[ mUpdatedFields[ *it ].name() ] = mUpdatedFields[ *it ].typeName();
if( (cap & QgsVectorDataProvider::AddAttributes ) && mDataProvider->addAttributes( addedAttributes ) )
{
mCommitErrors << tr( "SUCCESS: %1 attributes added." ).arg( mAddedAttributeIds.size() );
mAddedAttributeIds.clear();
attributesChanged = true;
}
else
{
mCommitErrors << tr( "ERROR: %1 new attributes not added" ).arg( mAddedAttributeIds.size() );
success = false;
}
}
//
// delete attributes
//
if ( mDeletedAttributeIds.size()>0 )
{
if( (cap & QgsVectorDataProvider::DeleteAttributes) && mDataProvider->deleteAttributes ( mDeletedAttributeIds ) )
{
mCommitErrors << tr( "SUCCESS: %1 attributes deleted." ).arg( mDeletedAttributeIds.size() );
mDeletedAttributeIds.clear();
attributesChanged = true;
}
else
{
mCommitErrors << tr( "ERROR: %1 attributes not deleted." ).arg( mDeletedAttributeIds.size() );
success = false;
}
}
//
// remap changed and attributes of added features
//
bool attributeChangesOk = true;
if( attributesChanged )
{
// map updates field indexes to names
QMap<int,QString> src;
for(QgsFieldMap::const_iterator it=mUpdatedFields.begin(); it!=mUpdatedFields.end(); it++)
{
src[ it.key() ] = it.value().name();
}
int maxAttrIdx = -1;
const QgsFieldMap &pFields = mDataProvider->fields();
// map provider table names to field indexes
QMap<QString,int> dst;
for(QgsFieldMap::const_iterator it=pFields.begin(); it!=pFields.end(); it++)
{
dst[ it.value().name() ] = it.key();
if( it.key() > maxAttrIdx )
maxAttrIdx = it.key();
}
// if adding attributes failed add fields that are now missing
// (otherwise we'll loose updates when doing the remapping)
if( mAddedAttributeIds.size()>0 )
{
for(QgsAttributeIds::const_iterator it=mAddedAttributeIds.begin(); it!=mAddedAttributeIds.end(); it++)
{
QString name = mUpdatedFields[ *it ].name();
if( dst.contains( name ) )
{
// it's there => so we don't need to add it anymore
mAddedAttributeIds.remove(*it);
mCommitErrors << tr( "SUCCESS: attribute %1 was added." ).arg( name );
}
else
{
// field not there => put it behind the existing attributes
dst[ name ] = ++maxAttrIdx;
attributeChangesOk = false; // don't try attribute updates - they'll fail.
mCommitErrors << tr( "ERROR: attribute %1 not added" ).arg( name );
}
}
}
// map updated fields to provider fields
QMap<int,int> remap;
for(QMap<int,QString>::const_iterator it=src.begin(); it!=src.end(); it++)
{
if( dst.contains(it.value()) )
{
remap[ it.key() ] = dst[ it.value() ];
}
}
// remap changed attributes
for(QgsChangedAttributesMap::iterator fit=mChangedAttributeValues.begin(); fit!=mChangedAttributeValues.end(); fit++)
{
QgsAttributeMap &src = fit.value();
QgsAttributeMap dst;
for(QgsAttributeMap::const_iterator it=src.begin(); it!=src.end(); it++)
if( remap.contains( it.key() ) )
dst[ remap[it.key()] ] = it.value();
src = dst;
}
// remap features of added attributes
for(QgsFeatureList::iterator fit=mAddedFeatures.begin(); fit!=mAddedFeatures.end(); fit++)
{
const QgsAttributeMap &src = fit->attributeMap();
QgsAttributeMap dst;
for(QgsAttributeMap::const_iterator it=src.begin(); it!=src.end(); it++)
if( remap.contains( it.key() ) )
dst[ remap[it.key()] ] = it.value();
fit->setAttributeMap(dst);
}
QgsFieldMap attributes;
// update private field map
for(QMap<int,int>::iterator it=remap.begin(); it!=remap.end(); it++)
attributes[ it.value() ] = mUpdatedFields[ it.key() ];
mUpdatedFields = attributes;
}
if(attributeChangesOk) {
//
// change attributes
//
if ( mChangedAttributeValues.size() >0 )
{
if( (cap & QgsVectorDataProvider::ChangeAttributeValues ) && mDataProvider->changeAttributeValues ( mChangedAttributeValues ) )
{
mCommitErrors << tr( "SUCCESS: %1 attribute values changed." ).arg( mChangedAttributeValues.size() );
mChangedAttributeValues.clear();
}
else
{
mCommitErrors << tr( "ERROR: %1 attribute value changes not applied." ).arg( mChangedAttributeValues.size() );
success = false;
}
}
//
// add features
//
if ( mAddedFeatures.size() >0 )
{
if( ( cap & QgsVectorDataProvider::AddFeatures ) && mDataProvider->addFeatures ( mAddedFeatures ) )
{
mCommitErrors << tr( "SUCCESS: %1 features added." ).arg( mAddedFeatures.size() );
mAddedFeatures.clear();
}
else
{
mCommitErrors << tr( "ERROR: %1 features not added." ).arg( mAddedFeatures.size() );
success = false;
}
}
}
//
// update geometries
//
if ( mChangedGeometries.size() > 0 )
{
if( (cap & QgsVectorDataProvider::ChangeGeometries) && mDataProvider->changeGeometryValues( mChangedGeometries ) )
{
mCommitErrors << tr ( "SUCCESS: %1 geometries were changed." ).arg( mChangedGeometries.size() );
mChangedGeometries.clear();
}
else
{
mCommitErrors << tr ( "ERROR: %1 geometries not changed." ).arg( mChangedGeometries.size() );
success = false;
}
}
//
// delete features
//
if ( mDeletedFeatureIds.size() > 0 )
{
if ( (cap & QgsVectorDataProvider::DeleteFeatures) && mDataProvider->deleteFeatures ( mDeletedFeatureIds ) )
{
mCommitErrors << tr ( "SUCCESS: %1 features deleted." ).arg( mDeletedFeatureIds.size() );
for(QgsFeatureIds::const_iterator it=mDeletedFeatureIds.begin(); it!=mDeletedFeatureIds.end(); it++)
{
mChangedAttributeValues.remove(*it);
mChangedGeometries.remove(*it);
}
mDeletedFeatureIds.clear();
}
else
{
mCommitErrors << tr ( "ERROR: %1 features not deleted." ).arg( mDeletedFeatureIds.size() );
success = false;
}
}
deleteCachedGeometries();
if(success)
{
mEditable = false;
setModified ( FALSE );
mUpdatedFields.clear();
mMaxUpdatedIndex = -1;
emit editingStopped();
}
mDataProvider->updateExtents();
mDataProvider->updateFeatureCount();
triggerRepaint();
QgsDebugMsg( "result:\n " + mCommitErrors.join("\n ") );
return success;
}
const QStringList &QgsVectorLayer::commitErrors()
{
return mCommitErrors;
}
bool QgsVectorLayer::rollBack()
{
if (!isEditable())
{
return false;
}
if (isModified())
{
while( mAddedAttributeIds.size()>0 )
{
int idx = *mAddedAttributeIds.begin();
mAddedAttributeIds.remove(idx);
mUpdatedFields.remove(idx);
emit attributeDeleted(idx);
}
while( mDeletedAttributeIds.size()>0 )
{
int idx = *mDeletedAttributeIds.begin();
mDeletedAttributeIds.remove(idx);
emit attributeAdded(idx);
}
// roll back changed attribute values
mChangedAttributeValues.clear();
// roll back changed geometries
mChangedGeometries.clear();
// Roll back added features
// Delete the features themselves before deleting the references to them.
mAddedFeatures.clear();
// Roll back deleted features
mDeletedFeatureIds.clear();
// clear private field map
mUpdatedFields.clear();
mMaxUpdatedIndex = -1;
}
deleteCachedGeometries();
mEditable = false;
emit editingStopped();
setModified(FALSE);
triggerRepaint();
return true;
}
void QgsVectorLayer::setSelectedFeatures(const QgsFeatureIds& ids)
{
// TODO: check whether features with these ID exist
mSelectedFeatureIds = ids;
emit selectionChanged();
}
int QgsVectorLayer::selectedFeatureCount()
{
return mSelectedFeatureIds.size();
}
const QgsFeatureIds& QgsVectorLayer::selectedFeaturesIds() const
{
return mSelectedFeatureIds;
}
QgsFeatureList QgsVectorLayer::selectedFeatures()
{
if (!mDataProvider)
{
return QgsFeatureList();
}
QgsFeatureList features;
QgsAttributeList allAttrs = mDataProvider->allAttributesList();
for (QgsFeatureIds::iterator it = mSelectedFeatureIds.begin(); it!=mSelectedFeatureIds.end(); ++it)
{
QgsFeature feat;
bool selectionIsAddedFeature = FALSE;
// Check this selected item against the uncommitted added features
for(QgsFeatureList::iterator iter = mAddedFeatures.begin(); iter != mAddedFeatures.end(); ++iter)
{
if ( *it == iter->featureId() )
{
feat = QgsFeature(*iter);
selectionIsAddedFeature = TRUE;
break;
}
}
// if the geometry is not newly added, get it from provider
if (!selectionIsAddedFeature)
{
mDataProvider->getFeatureAtId(*it, feat, true, allAttrs);
}
updateFeatureAttributes(feat);
updateFeatureGeometry(feat);
features << feat;
} // for each selected
return features;
}
bool QgsVectorLayer::addFeatures(QgsFeatureList features, bool makeSelected)
{
if (!mDataProvider)
{
return false;
}
if(!(mDataProvider->capabilities() & QgsVectorDataProvider::AddFeatures))
{
return false;
}
if (!isEditable())
{
return false;
}
if (makeSelected)
{
mSelectedFeatureIds.clear();
}
for (QgsFeatureList::iterator iter = features.begin(); iter != features.end(); ++iter)
{
addFeature(*iter);
if (makeSelected)
{
mSelectedFeatureIds.insert( iter->featureId() );
}
}
updateExtents();
if (makeSelected)
{
emit selectionChanged();
}
return true;
}
bool QgsVectorLayer::copySymbologySettings(const QgsMapLayer& other)
{
const QgsVectorLayer* vl = dynamic_cast<const QgsVectorLayer*>(&other);
// exit if both vectorlayer are the same
if(this == vl)
{
return false;
}
if(!vl)
{
return false;
}
delete mRenderer;
QgsRenderer* r = vl->mRenderer;
if(r)
{
mRenderer = r->clone();
return true;
}
else
{
return false;
}
}
bool QgsVectorLayer::isSymbologyCompatible(const QgsMapLayer& other) const
{
// vector layers are symbology compatible if they have the same type, the same sequence of numerical/ non numerical fields and the same field names
const QgsVectorLayer* otherVectorLayer = dynamic_cast<const QgsVectorLayer*>(&other);
if(otherVectorLayer)
{
if(otherVectorLayer->vectorType() != vectorType())
{
return false;
}
const QgsFieldMap& fieldsThis = mDataProvider->fields();
const QgsFieldMap& fieldsOther = otherVectorLayer ->mDataProvider->fields();
if(fieldsThis.size() != fieldsOther.size())
{
return false;
}
// TODO: fill two sets with the numerical types for both layers
uint fieldsThisSize = fieldsThis.size();
for(uint i = 0; i < fieldsThisSize; ++i)
{
if(fieldsThis[i].name() != fieldsOther[i].name()) // field names need to be the same
{
return false;
}
// TODO: compare types of the fields
}
return true; // layers are symbology compatible if the code reaches this point
}
return false;
}
bool QgsVectorLayer::snapPoint(QgsPoint& point, double tolerance)
{
QMultiMap<double, QgsSnappingResult> snapResults;
int result = snapWithContext(point, tolerance, snapResults, QgsSnapper::SNAP_TO_VERTEX);
if(result != 0)
{
return false;
}
if(snapResults.size() < 1)
{
return false;
}
QMultiMap<double, QgsSnappingResult>::const_iterator snap_it = snapResults.constBegin();
point.setX(snap_it.value().snappedVertex.x());
point.setY(snap_it.value().snappedVertex.y());
return true;
}
int QgsVectorLayer::snapWithContext(const QgsPoint& startPoint, double snappingTolerance,
QMultiMap<double, QgsSnappingResult>& snappingResults,
QgsSnapper::SNAP_TO snap_to)
{
if (snappingTolerance<=0 || !mDataProvider)
{
return 1;
}
QList<QgsFeature> featureList;
QgsRect searchRect(startPoint.x()-snappingTolerance, startPoint.y()-snappingTolerance,
startPoint.x()+snappingTolerance, startPoint.y()+snappingTolerance);
double sqrSnappingTolerance = snappingTolerance * snappingTolerance;
select(QgsAttributeList(), searchRect, true);
int n=0;
QgsFeature f;
while( getNextFeature(f) )
{
snapToGeometry(startPoint, f.featureId(), f.geometry(), sqrSnappingTolerance, snappingResults, snap_to);
}
return n==0 ? 2 : 0;
}
void QgsVectorLayer::snapToGeometry(const QgsPoint& startPoint, int featureId, QgsGeometry* geom, double sqrSnappingTolerance,
QMultiMap<double, QgsSnappingResult>& snappingResults, QgsSnapper::SNAP_TO snap_to) const
{
if(!geom)
{
return;
}
int atVertex, beforeVertex, afterVertex;
double sqrDistVertexSnap, sqrDistSegmentSnap;
QgsPoint snappedPoint;
QgsSnappingResult snappingResultVertex;
QgsSnappingResult snappingResultSegment;
if(snap_to == QgsSnapper::SNAP_TO_VERTEX || snap_to == QgsSnapper::SNAP_TO_VERTEX_AND_SEGMENT)
{
snappedPoint = geom->closestVertex(startPoint, atVertex, beforeVertex, afterVertex, sqrDistVertexSnap);
if(sqrDistVertexSnap < sqrSnappingTolerance)
{
snappingResultVertex.snappedVertex = snappedPoint;
snappingResultVertex.snappedVertexNr = atVertex;
snappingResultVertex.beforeVertexNr = beforeVertex;
if(beforeVertex != -1) // make sure the vertex is valid
{
snappingResultVertex.beforeVertex = geom->vertexAt(beforeVertex);
}
snappingResultVertex.afterVertexNr = afterVertex;
if(afterVertex != -1) // make sure the vertex is valid
{
snappingResultVertex.afterVertex = geom->vertexAt(afterVertex);
}
snappingResultVertex.snappedAtGeometry = featureId;
snappingResultVertex.layer = this;
snappingResults.insert(sqrt(sqrDistVertexSnap), snappingResultVertex);
return;
}
}
if(snap_to == QgsSnapper::SNAP_TO_SEGMENT || snap_to == QgsSnapper::SNAP_TO_VERTEX_AND_SEGMENT) // snap to segment
{
if(vectorType() != QGis::Point) // cannot snap to segment for points/multipoints
{
sqrDistSegmentSnap = geom->closestSegmentWithContext(startPoint, snappedPoint, afterVertex);
if(sqrDistSegmentSnap < sqrSnappingTolerance)
{
snappingResultSegment.snappedVertex = snappedPoint;
snappingResultSegment.snappedVertexNr = -1;
snappingResultSegment.beforeVertexNr = afterVertex - 1;
snappingResultSegment.afterVertexNr = afterVertex;
snappingResultSegment.snappedAtGeometry = featureId;
snappingResultSegment.beforeVertex = geom->vertexAt(afterVertex - 1);
snappingResultSegment.afterVertex = geom->vertexAt(afterVertex);
snappingResultSegment.layer = this;
snappingResults.insert(sqrt(sqrDistSegmentSnap), snappingResultSegment);
}
}
}
}
int QgsVectorLayer::insertSegmentVerticesForSnap(const QList<QgsSnappingResult>& snapResults)
{
int returnval = 0;
QgsPoint layerPoint;
QList<QgsSnappingResult>::const_iterator it=snapResults.constBegin();
for(; it != snapResults.constEnd(); ++it)
{
if(it->snappedVertexNr == -1) // segment snap
{
layerPoint = it->snappedVertex;
if(!insertVertexBefore(layerPoint.x(), layerPoint.y(), it->snappedAtGeometry, it->afterVertexNr))
{
returnval = 3;
}
}
}
return returnval;
}
int QgsVectorLayer::boundingBoxFromPointList(const QList<QgsPoint>& list, double& xmin, double& ymin, double& xmax, double& ymax) const
{
if(list.size() < 1)
{
return 1;
}
xmin = std::numeric_limits<double>::max();
xmax = -std::numeric_limits<double>::max();
ymin = std::numeric_limits<double>::max();
ymax = -std::numeric_limits<double>::max();
for(QList<QgsPoint>::const_iterator it = list.constBegin(); it != list.constEnd(); ++it)
{
if(it->x() < xmin)
{
xmin = it->x();
}
if(it->x() > xmax)
{
xmax = it->x();
}
if(it->y() < ymin)
{
ymin = it->y();
}
if(it->y() > ymax)
{
ymax = it->y();
}
}
return 0;
}
QgsVectorLayer::VertexMarkerType QgsVectorLayer::currentVertexMarkerType()
{
QSettings settings;
QString markerTypeString = settings.value("/qgis/digitizing/marker_style", "SemiTransparentCircle").toString();
if(markerTypeString == "Cross")
{
return QgsVectorLayer::Cross;
}
else
{
return QgsVectorLayer::SemiTransparentCircle;
}
}
void QgsVectorLayer::drawFeature(QPainter* p,
QgsFeature& fet,
const QgsMapToPixel* theMapToPixelTransform,
const QgsCoordinateTransform* ct,
QImage * marker,
double widthScale,
double rasterScaleFactor,
bool drawingToEditingCanvas)
{
// Only have variables, etc outside the switch() statement that are
// used in all cases of the statement (otherwise they may get
// executed, but never used, in a bit of code where performance is
// critical).
#if defined(Q_WS_X11)
bool needToTrim = false;
#endif
QgsGeometry* geom = fet.geometry();
unsigned char* feature = geom->wkbBuffer();
QGis::WKBTYPE wkbType = geom->wkbType();
switch (wkbType)
{
case QGis::WKBPoint:
case QGis::WKBPoint25D:
{
double x = *((double *) (feature + 5));
double y = *((double *) (feature + 5 + sizeof(double)));
transformPoint(x, y, theMapToPixelTransform, ct);
//QPointF pt(x - (marker->width()/2), y - (marker->height()/2));
QPointF pt(x*rasterScaleFactor - (marker->width()/2), y*rasterScaleFactor - (marker->height()/2));
p->save();
//p->scale(markerScaleFactor,markerScaleFactor);
p->scale(1.0/rasterScaleFactor, 1.0/rasterScaleFactor);
p->drawImage(pt, *marker);
p->restore();
break;
}
case QGis::WKBMultiPoint:
case QGis::WKBMultiPoint25D:
{
unsigned char *ptr = feature + 5;
unsigned int nPoints = *((int*)ptr);
ptr += 4;
p->save();
//p->scale(markerScaleFactor, markerScaleFactor);
p->scale(1.0/rasterScaleFactor, 1.0/rasterScaleFactor);
for (register unsigned int i = 0; i < nPoints; ++i)
{
ptr += 5;
double x = *((double *) ptr);
ptr += sizeof(double);
double y = *((double *) ptr);
ptr += sizeof(double);
if (wkbType == QGis::WKBMultiPoint25D) // ignore Z value
ptr += sizeof(double);
#ifdef QGISDEBUG
std::cout <<"...WKBMultiPoint (" << x << ", " << y << ")" <<std::endl;
#endif
transformPoint(x, y, theMapToPixelTransform, ct);
//QPointF pt(x - (marker->width()/2), y - (marker->height()/2));
//QPointF pt(x/markerScaleFactor - (marker->width()/2), y/markerScaleFactor - (marker->height()/2));
QPointF pt(x, y);
#if defined(Q_WS_X11)
// Work around a +/- 32768 limitation on coordinates in X11
if (std::abs(x) > QgsClipper::maxX ||
std::abs(y) > QgsClipper::maxY)
needToTrim = true;
else
#endif
p->drawImage(pt, *marker);
}
p->restore();
break;
}
case QGis::WKBLineString:
case QGis::WKBLineString25D:
{
drawLineString(feature,
p,
theMapToPixelTransform,
ct,
drawingToEditingCanvas);
break;
}
case QGis::WKBMultiLineString:
case QGis::WKBMultiLineString25D:
{
unsigned char* ptr = feature + 5;
unsigned int numLineStrings = *((int*)ptr);
ptr = feature + 9;
for (register unsigned int jdx = 0; jdx < numLineStrings; jdx++)
{
ptr = drawLineString(ptr,
p,
theMapToPixelTransform,
ct,
drawingToEditingCanvas);
}
break;
}
case QGis::WKBPolygon:
case QGis::WKBPolygon25D:
{
drawPolygon(feature,
p,
theMapToPixelTransform,
ct,
drawingToEditingCanvas);
break;
}
case QGis::WKBMultiPolygon:
case QGis::WKBMultiPolygon25D:
{
unsigned char *ptr = feature + 5;
unsigned int numPolygons = *((int*)ptr);
ptr = feature + 9;
for (register unsigned int kdx = 0; kdx < numPolygons; kdx++)
ptr = drawPolygon(ptr,
p,
theMapToPixelTransform,
ct,
drawingToEditingCanvas);
break;
}
default:
QgsDebugMsg("UNKNOWN WKBTYPE ENCOUNTERED");
break;
}
}
void QgsVectorLayer::setCoordinateSystem()
{
QgsDebugMsg("QgsVectorLayer::setCoordinateSystem ----- Computing Coordinate System");
//
// Get the layers project info and set up the QgsCoordinateTransform
// for this layer
//
// get SRS directly from provider
*mSRS = mDataProvider->getSRS();
//QgsSpatialRefSys provides a mechanism for FORCE a srs to be valid
//which is inolves falling back to system, project or user selected
//defaults if the srs is not properly intialised.
//we only nee to do that if the srs is not alreay valid
if (!mSRS->isValid())
{
mSRS->validate();
}
}
// Convenience function to transform the given point
inline void QgsVectorLayer::transformPoint(
double& x, double& y,
const QgsMapToPixel* mtp,
const QgsCoordinateTransform* ct)
{
// transform the point
if (ct)
{
double z = 0;
ct->transformInPlace(x, y, z);
}
// transform from projected coordinate system to pixel
// position on map canvas
mtp->transformInPlace(x, y);
}
inline void QgsVectorLayer::transformPoints(
std::vector<double>& x, std::vector<double>& y, std::vector<double>& z,
const QgsMapToPixel* mtp, const QgsCoordinateTransform* ct)
{
// transform the point
if (ct)
ct->transformInPlace(x, y, z);
// transform from projected coordinate system to pixel
// position on map canvas
mtp->transformInPlace(x, y);
}
const QString QgsVectorLayer::displayField() const
{
return mDisplayField;
}
bool QgsVectorLayer::isEditable() const
{
return (mEditable && mDataProvider);
}
bool QgsVectorLayer::isModified() const
{
return mModified;
}
void QgsVectorLayer::setModified(bool modified, bool onlyGeometry)
{
mModified = modified;
emit wasModified(onlyGeometry);
}
QgsVectorLayer::EditType QgsVectorLayer::editType(int idx)
{
const QgsFieldMap &fields = pendingFields();
if( fields.contains(idx) && mEditTypes.contains( fields[idx].name() ) )
return mEditTypes[ fields[idx].name() ];
else
return LineEdit;
}
void QgsVectorLayer::setEditType(int idx, EditType type)
{
const QgsFieldMap &fields = pendingFields();
if( fields.contains(idx) )
mEditTypes[ fields[idx].name() ] = type;
}
QMap< QString, QVariant > &QgsVectorLayer::valueMap(int idx)
{
const QgsFieldMap &fields = pendingFields();
// FIXME: throw an exception!?
if( fields.contains(idx) )
QgsDebugMsg( QString("field %1 not found").arg(idx) );
if( !mValueMaps.contains( fields[idx].name() ) )
mValueMaps[ fields[idx].name() ] = QMap<QString, QVariant>();
return mValueMaps[ fields[idx].name() ];
}
QgsVectorLayer::RangeData &QgsVectorLayer::range(int idx)
{
const QgsFieldMap &fields = pendingFields();
// FIXME: throw an exception!?
if( fields.contains(idx) )
QgsDebugMsg( QString("field %1 not found").arg(idx) );
if( !mRanges.contains( fields[idx].name() ) )
mRanges[ fields[idx].name() ] = RangeData();
return mRanges[ fields[idx].name() ];
}