QGIS/src/core/qgsvectorlayer.cpp

3285 lines
86 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 "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)
{
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");
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
{
QgsFieldMap fields = mDataProvider->fields();
int fieldsSize = fields.size();
for (QgsFieldMap::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.find("name", false) > -1)
{
if(idxName.isEmpty())
{
idxName = fldName;
}
}
if (fldName.find("descrip", false) > -1)
{
if(idxName.isEmpty())
{
idxName = fldName;
}
}
if (fldName.find("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(QPainter * p, QgsRect & viewExtent, QgsMapToPixel * theMapToPixelTransform, QgsCoordinateTransform* ct)
{
drawLabels(p, viewExtent, theMapToPixelTransform, ct, 1.);
}
// 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, QgsRect & viewExtent, QgsMapToPixel * theMapToPixelTransform, 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;
// select the records in the extent. The provider sets a spatial filter
// and sets up the selection set for retrieval
mDataProvider->select(attributes, viewExtent);
try
{
QgsFeature fet;
// main render loop
while(mDataProvider->getNextFeature(fet))
{
// don't render labels of deleted features
if (!mDeletedFeatureIds.contains(fet.featureId()))
{
if(mRenderer->willRenderFeature(&fet))
{
bool sel = mSelectedFeatureIds.contains(fet.featureId());
mLabel->renderLabel ( p, viewExtent, ct,
theMapToPixelTransform, fet, sel, 0, scale);
}
}
featureCount++;
}
// render labels of not-commited features
for (QgsFeatureList::iterator it = mAddedFeatures.begin(); it != mAddedFeatures.end(); ++it)
{
if(mRenderer->willRenderFeature(&(*it)))
{
bool sel = mSelectedFeatureIds.contains((*it).featureId());
mLabel->renderLabel ( p, viewExtent, ct, theMapToPixelTransform, *it, sel, 0, scale);
}
}
}
catch (QgsCsException &e)
{
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,
QgsMapToPixel* mtp,
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,
QgsMapToPixel* mtp,
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.
/*
#ifdef QGISDEBUG
std::cerr << "Points for ring " << idx << " ("
<< nPoints << " points)\n";
#endif
*/
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);
/*
#ifdef QGISDEBUG
std::cerr << jdx << ": "
<< ring->first[jdx] << ", " << ring->second[jdx] << '\n';
#endif
*/
}
// 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);
/*
#ifdef QGISDEBUG
std::cerr << "Trimmed points (" << ring->first.size() << ")\n";
for (int i = 0; i < ring->first.size(); ++i)
std::cerr << i << ": " << ring->first[i]
<< ", " << ring->second[i] << '\n';
#endif
*/
break;
}
//std::cout << "POLYGONTRANSFORM: " << ring->first[i] << ", " << ring->second[i] << std::endl;
}
#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(QPainter * p,
QgsRect & viewExtent,
QgsMapToPixel * theMapToPixelTransform,
QgsCoordinateTransform* ct,
bool drawingToEditingCanvas)
{
//set update threshold before each draw to make sure the current setting is picked up
QSettings settings;
mUpdateThreshold = settings.readNumEntry("Map/updateThreshold", 0);
draw ( p, viewExtent, theMapToPixelTransform, ct, drawingToEditingCanvas, 1., 1.);
return TRUE; // Assume success always
}
void QgsVectorLayer::draw(QPainter * p,
QgsRect & viewExtent,
QgsMapToPixel * theMapToPixelTransform,
QgsCoordinateTransform* ct,
bool drawingToEditingCanvas,
double widthScale,
double symbolScale)
{
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 = symbolScale;
if(mEditable)
{
// Destroy all cached geometries and clear the references to them
deleteCachedGeometries();
}
mDataProvider->updateFeatureCount();
int totalFeatures = mDataProvider->featureCount();
int featureCount = 0;
QgsFeature fet;
QgsAttributeList attributes = mRenderer->classificationAttributes();
mDataProvider->select(attributes, viewExtent);
try
{
while (mDataProvider->getNextFeature(fet))
{
// 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(); //so we can trap for esc press
//if (mDrawingCancelled) return;
// If update threshold is greater than 0, check to see if
// the threshold has been exceeded
if(mUpdateThreshold > 0)
{
// signal progress in drawing
if(0 == featureCount % mUpdateThreshold)
{
emit screenUpdateRequested();
emit drawingProgress(featureCount, totalFeatures);
qApp->processEvents();
}
}
if (mEditable)
{
// Cache this for the use of (e.g.) modifying the feature's geometry.
// mCachedGeometries[fet->featureId()] = fet->geometryAndOwnership();
if (mDeletedFeatureIds.contains(fet.featureId()))
{
continue; //dont't draw feature marked as deleted
}
if (mChangedGeometries.contains(fet.featureId()))
{
// substitute the committed geometry with the modified one
fet.setGeometry( mChangedGeometries[ fet.featureId() ] );
}
// 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;
if (
/*(mLegend->currentLayer() == this) && TODO!*/
(mSelectedFeatureIds.contains(fet.featureId()))
)
{
sel = TRUE;
}
else
{
sel = FALSE;
}
//QgsDebugMsg(QString("markerScale before renderFeature(): %1").arg(markerScaleFactor));
// markerScalerFactore reflects the wanted scaling of the marker
mRenderer->renderFeature(p, fet, &marker, &markerScaleFactor, sel, widthScale );
// markerScalerFactore now reflects the actual scaling of the marker that the render performed.
//QgsDebugMsg(QString("markerScale after renderFeature(): %1").arg(markerScaleFactor));
double scale = symbolScale / markerScaleFactor;
drawFeature(p,fet,theMapToPixelTransform,ct, &marker, scale, drawingToEditingCanvas);
++featureCount;
}
// also draw the not yet commited features
if (mEditable)
{
QgsFeatureList::iterator it = mAddedFeatures.begin();
for(; it != mAddedFeatures.end(); ++it)
{
bool sel = mSelectedFeatureIds.contains((*it).featureId());
QgsDebugMsg(QString("markerScale before renderFeature(): %1").arg(markerScaleFactor));
// markerScalerFactore reflects the wanted scaling of the marker
mRenderer->renderFeature(p, *it, &marker, &markerScaleFactor,
sel, widthScale);
// markerScalerFactore now reflects the actual scaling of the marker that the render performed.
QgsDebugMsg(QString("markerScale after renderFeature(): %1").arg(markerScaleFactor));
double scale = symbolScale / markerScaleFactor;
if (mChangedGeometries.contains((*it).featureId()))
{
(*it).setGeometry( mChangedGeometries[(*it).featureId() ] );
}
// give a deep copy of the geometry to mCachedGeometry because it will be erased at each redraw
mCachedGeometries.insert((*it).featureId(), QgsGeometry(*((*it).geometry())) );
drawFeature(p,*it,theMapToPixelTransform,ct, &marker,scale, drawingToEditingCanvas);
}
}
}
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);
}
QgsDebugMsg("Total features processed is " + QString::number(featureCount));
// 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();
}
else
{
QgsLogger::warning("QgsRenderer is null in QgsVectorLayer::draw()");
}
}
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
QList<QgsFeature> selectedList;
featuresInRectangle(rect, selectedList, true, false); //should finally be false, false
QList<QgsFeature>::const_iterator select_it = selectedList.constBegin();
for(; select_it != selectedList.constEnd(); ++select_it)
{
select(select_it->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;
for(QgsFeatureIds::iterator iter = mSelectedFeatureIds.begin(); iter != mSelectedFeatureIds.end(); ++iter)
{
tmp.insert(*iter);
}
removeSelection(FALSE); // don't emit signal
QgsFeature fet;
mDataProvider->select();
while (mDataProvider->getNextFeature(fet))
{
// don't select deleted features
if (!mDeletedFeatureIds.contains(fet.featureId()))
{
select(fet.featureId(), FALSE); // don't emit signal
}
}
// consider also newly added features
for (QgsFeatureList::iterator iter = mAddedFeatures.begin(); iter != mAddedFeatures.end(); ++iter)
{
select((*iter).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;
QgsFeature fet;
mDataProvider->select();
retval.setMinimal();
while (mDataProvider->getNextFeature(fet))
{
if (mSelectedFeatureIds.contains(fet.featureId()))
{
if(fet.geometry())
{
r=fet.geometry()->boundingBox();
retval.combineExtentWith(&r);
}
}
}
// also go through the not commited features
for(QgsFeatureList::iterator iter = mAddedFeatures.begin(); iter != mAddedFeatures.end(); ++iter)
{
if(mSelectedFeatureIds.contains((*iter).featureId()))
{
r = (*iter).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)
{
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);
}
}
else
{
QgsFeature fet;
QgsRect bb;
mDataProvider->select();
while (mDataProvider->getNextFeature(fet))
{
if (!mDeletedFeatureIds.contains(fet.featureId()))
{
if (fet.geometry())
{
bb = fet.geometry()->boundingBox();
mLayerExtent.combineExtentWith(&bb);
}
}
}
}
}
else
{
QgsLogger::warning(" QgsVectorLayer::updateFeatureCount() invoked with null mDataProvider");
}
// also consider the not commited features
for(QgsFeatureList::iterator iter = mAddedFeatures.begin(); iter != mAddedFeatures.end(); ++iter)
{
QgsRect bb = iter->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();
}
int QgsVectorLayer::featuresInRectangle(const QgsRect& searchRect, QList<QgsFeature>& features, bool fetchGeometries, bool fetchAttributes)
{
if(!mDataProvider)
{
return 1;
}
QSet<int> alreadyExamined;
QgsGeometry* currentGeomPointer = 0;
//first search all changed geometries
QgsGeometryMap::iterator changedIt;
for(changedIt = mChangedGeometries.begin(); changedIt != mChangedGeometries.end(); ++changedIt)
{
alreadyExamined.insert(changedIt.key());
if(mDeletedFeatureIds.contains(changedIt.key()))
{
continue;
}
if(changedIt->intersects(searchRect))
{
QgsFeature newFeature(changedIt.key());
if(fetchGeometries)
{
currentGeomPointer = new QgsGeometry(changedIt.value());
newFeature.setGeometry(currentGeomPointer);
}
if(fetchAttributes)
{
QgsFeature tmpFeature;
mDataProvider->getFeatureAtId(changedIt.key(), tmpFeature, false, mDataProvider->allAttributesList());
newFeature.setAttributeMap(tmpFeature.attributeMap());
}
features.push_back(newFeature);
}
}
//then the added features
for (QgsFeatureList::iterator iter = mAddedFeatures.begin(); iter != mAddedFeatures.end(); ++iter)
{
if(alreadyExamined.contains(iter->featureId()))
{
continue;
}
if(iter->geometry() && iter->geometry()->intersects(searchRect))
{
QgsFeature newFeature(iter->featureId());
if(fetchGeometries)
{
currentGeomPointer = new QgsGeometry(*iter->geometry());
newFeature.setGeometry(currentGeomPointer);
}
if(fetchAttributes)
{
newFeature.setAttributeMap(iter->attributeMap());
}
features.push_back(newFeature);
}
}
//look in the normal features of the provider
if(mDataProvider)
{
mDataProvider->select(mDataProvider->allAttributesList(), searchRect, fetchGeometries, true);
}
else
{
mDataProvider->select(QgsAttributeList(), searchRect, fetchGeometries, true);
}
QgsFeature f;
while(getDataProvider() && getDataProvider()->getNextFeature(f))
{
if(mChangedGeometries.contains(f.featureId()) || mDeletedFeatureIds.contains(f.featureId()))
{
continue;
}
QgsFeature newFeature(f.featureId());
if(fetchGeometries)
{
currentGeomPointer = new QgsGeometry(*(f.geometry()));
newFeature.setGeometry(currentGeomPointer);
}
if(fetchAttributes)
{
newFeature.setAttributeMap(f.attributeMap());
}
features.push_back(newFeature);
}
return 0;
}
int QgsVectorLayer::getFeatureAtId(int featureId, QgsFeature& f, bool fetchGeometries, bool fetchAttributes)
{
if(!mDataProvider)
{
return 1;
}
if(mDeletedFeatureIds.contains(featureId))
{
return 2;
}
//changed geometries
QgsGeometryMap::iterator changedIt;
changedIt = mChangedGeometries.find(featureId);
if(changedIt != mChangedGeometries.end())
{
f.setFeatureId(changedIt.key());
if(fetchGeometries)
{
f.setGeometry(new QgsGeometry(changedIt.value()));
}
if(fetchAttributes)
{
QgsFeature tmpFeature;
mDataProvider->getFeatureAtId(changedIt.key(), tmpFeature, false, mDataProvider->allAttributesList());
f.setAttributeMap(tmpFeature.attributeMap());
}
return 0;
}
//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;
}
}
//permanent features
if(fetchAttributes)
{
if(mDataProvider->getFeatureAtId(featureId, f, fetchGeometries, mDataProvider->allAttributesList()))
{
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;
}
for(QgsFeatureIds::iterator it = mSelectedFeatureIds.begin(); it != mSelectedFeatureIds.end(); ++it)
{
bool noncommited = FALSE;
// first test, if the feature with this id is a not-commited feature
for (QgsFeatureList::iterator iter = mAddedFeatures.begin(); iter != mAddedFeatures.end(); ++iter)
{
if (iter->featureId() == *it)
{
noncommited = TRUE;
// Delete the feature itself
mAddedFeatures.remove(iter);
break;
}
}
if (!noncommited)
{
mDeletedFeatureIds.insert(*it);
}
}
if(mSelectedFeatureIds.size()>0)
{
setModified(TRUE);
removeSelection(FALSE); // don't emit signal
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
}
QList<QgsFeature> featureList;
featuresInRectangle(bBox, featureList, true, false);
QList<QgsFeature>::iterator f_it = featureList.begin();
for(; f_it != featureList.end(); ++f_it)
{
addRingReturnCode = f_it->geometry()->addRing(ring);
if(addRingReturnCode == 0)
{
mChangedGeometries.insert(f_it->featureId(), *(f_it->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;
}
}
featuresInRectangle(bBox, featureList);
}
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
QList<QgsFeature> featureList;
featuresInRectangle(geomBBox, featureList);
QList<QgsFeature>::iterator it = featureList.begin();
QgsGeometry* currentGeom;
for(; it != featureList.end(); ++it)
{
//call geometry->makeDifference for each feature
currentGeom = it->geometry();
if(currentGeom)
{
if(geom->makeDifference(it->geometry()) != 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;
}
mEditable=true;
return true;
}
bool QgsVectorLayer::readXML_( QDomNode & layer_node )
{
#ifdef QGISDEBUG
std::cerr << "Datasource in QgsVectorLayer::readXML_: " << mDataSource.toLocal8Bit().data() << std::endl;
#endif
// 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());
}
// 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);
}
#ifdef QGISDEBUG
std::cout << "Testing if qgsvectorlayer can call label readXML routine" << std::endl;
#endif
QDomNode labelattributesnode = layer_node.namedItem("labelattributes");
if(!labelattributesnode.isNull())
{
#ifdef QGISDEBUG
std::cout << "qgsvectorlayer calling label readXML routine" << std::endl;
#endif
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());
}
// 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);
}
// 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_
int QgsVectorLayer::findFreeId()
{
int freeid=-INT_MAX;
int fid;
if(mDataProvider)
{
mDataProvider->select();
QgsFeature fet;
//TODO: Is there an easier way of doing this other than iteration?
//TODO: Also, what about race conditions between this code and a competing mapping client?
//TODO: Maybe push this to the data provider?
while (mDataProvider->getNextFeature(fet))
{
fid = fet.featureId();
if(fid>freeid)
{
freeid=fid;
}
}
QgsDebugMsg("freeid is: " + QString::number(freeid+1));
return freeid+1;
}
else
{
QgsDebugMsg("Error, mDataProvider is 0 in QgsVectorLayer::findFreeId");
return -1;
}
}
bool QgsVectorLayer::commitChanges()
{
if (!mDataProvider)
{
return false;
}
if (!isEditable())
{
return false;
}
/*
Unfortunately the commits occur in four distinct stages,
(add features, change attributes, change geometries, delete features)
so if a stage fails, it's difficult to roll back cleanly.
Therefore the error messages become a bit complicated to generate.
*/
// Attempt the commit of new features
bool addedFeaturesOk = FALSE;
if (mAddedFeatures.size() > 0)
{
if(!mDataProvider->addFeatures(mAddedFeatures))
{
QStringList errorStrings;
errorStrings += tr("Could not commit the added features.");
errorStrings += tr("No other types of changes will be committed at this time.");
// TODO: use an error string or something to store error to be retrieved later [MD]
//QMessageBox::warning(0, tr("Error"), errorStrings.join(" "));
return FALSE;
}
else
{
// done, remove features from the list
mAddedFeatures.clear();
addedFeaturesOk = TRUE;
}
}
// Attempt the commit of changed attributes
bool changedAttributesOk = FALSE;
if (mChangedAttributes.size() > 0)
{
if (!mDataProvider->changeAttributeValues(mChangedAttributes))
{
QStringList errorStrings;
errorStrings += tr("Could not commit the changed attributes.");
if (addedFeaturesOk)
{
errorStrings += tr("However, the added features were committed OK.");
}
errorStrings += tr("No other types of changes will be committed at this time.");
// TODO: use an error string or something to store error to be retrieved later [MD]
//QMessageBox::warning(0, tr("Error"), errorStrings.join(" "));
return FALSE;
}
else
{
// Changed attributes committed OK, remove the in-memory changes
mChangedAttributes.clear();
changedAttributesOk = TRUE;
}
}
// Attempt the commit of changed geometries
bool changedGeometriesOk = FALSE;
if (mChangedGeometries.size() > 0)
{
if (!mDataProvider->changeGeometryValues(mChangedGeometries))
{
QStringList errorStrings;
errorStrings += tr("Could not commit the changed geometries.");
if (addedFeaturesOk)
{
errorStrings += tr("However, the added features were committed OK.");
}
if (changedAttributesOk)
{
errorStrings += tr("However, the changed attributes were committed OK.");
}
errorStrings += tr("No other types of changes will be committed at this time.");
// TODO: use an error string or something to store error to be retrieved later [MD]
//QMessageBox::warning(0, tr("Error"), errorStrings.join(" "));
return FALSE;
}
else
{
// Changed geometries committed OK, remove the in-memory changes
mChangedGeometries.clear();
changedGeometriesOk = TRUE;
}
}
// Attempt the commit of deleted features
bool deletedFeaturesOk = FALSE;
if (mDeletedFeatureIds.size() > 0)
{
if (!mDataProvider->deleteFeatures(mDeletedFeatureIds))
{
QStringList errorStrings;
errorStrings += tr("Could not commit the deleted features.");
if (addedFeaturesOk)
{
errorStrings += tr("However, the added features were committed OK.");
}
if (changedAttributesOk)
{
errorStrings += tr("However, the changed attributes were committed OK.");
}
if (changedGeometriesOk)
{
errorStrings += tr("However, the changed geometries were committed OK.");
}
errorStrings += tr("No other types of changes will be committed at this time.");
// TODO: use an error string or something to store error to be retrieved later [MD]
//QMessageBox::warning(0, tr("Error"), errorStrings.join(" "));
return FALSE;
}
else
{
// just in case some of those features are still selected
for (QgsFeatureIds::iterator it = mDeletedFeatureIds.begin(); it != mDeletedFeatureIds.end(); ++it)
{
mSelectedFeatureIds.remove(*it);
}
// Deleted features committed OK, remove the in-memory changes
mDeletedFeatureIds.clear();
deletedFeaturesOk = TRUE;
}
}
deleteCachedGeometries();
mEditable = false;
setModified(FALSE);
mDataProvider->updateExtents();
mDataProvider->updateFeatureCount();
triggerRepaint();
return TRUE;
}
bool QgsVectorLayer::rollBack()
{
if (!isEditable())
{
return false;
}
if (isModified())
{
// roll back changed attributes
mChangedAttributes.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();
}
deleteCachedGeometries();
mEditable = false;
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);
}
// Transform the feature to the "current" in-memory version
features.append(QgsFeature(feat, mChangedAttributes, mChangedGeometries));
} // 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);
if(this == vl)//exit if both vectorlayer are the same
{
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;
if(featuresInRectangle(searchRect, featureList, true, false) != 0)
{
return 2;
}
QList<QgsFeature>::iterator feature_it = featureList.begin();
for(; feature_it != featureList.end(); ++feature_it)
{
snapToGeometry(startPoint, feature_it->featureId(), feature_it->geometry(), sqrSnappingTolerance, snappingResults, snap_to);
}
return 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,
QgsMapToPixel * theMapToPixelTransform,
QgsCoordinateTransform* ct,
QImage * marker,
double markerScaleFactor,
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();
#ifdef QGISDEBUG
//std::cout <<"Entering drawFeature()" << std::endl;
#endif
switch (wkbType)
{
case QGis::WKBPoint:
case QGis::WKBPoint25D:
{
double x = *((double *) (feature + 5));
double y = *((double *) (feature + 5 + sizeof(double)));
#ifdef QGISDEBUG
// std::cout <<"...WKBPoint (" << x << ", " << y << ")" <<std::endl;
#endif
//QgsDebugMsg(QString("markerScaleFactor = %1").arg(markerScaleFactor));
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));
p->save();
p->scale(markerScaleFactor,markerScaleFactor);
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);
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));
#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();
/*
// XXX old stuff
int srid = getProjectionSrid();
if(srid == 0)
{
QString mySourceWKT(getProjectionWKT());
if (mySourceWKT.isNull())
{
mySourceWKT=QString("");
}
QgsDebugMsg("QgsVectorLayer::setCoordinateSystem --- using wkt " + mySourceWKT);
mSRS->createFromWkt(mySourceWKT);
}
else
{
QgsDebugMsg("QgsVectorLayer::setCoordinateSystem --- using srid " + QString::number(srid));
mSRS->createFromSrid(srid);
}
*/
//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();
}
}
bool QgsVectorLayer::commitAttributeChanges(const QgsAttributeIds& deleted,
const QgsNewAttributesMap& added,
const QgsChangedAttributesMap& changed)
{
bool returnvalue=true;
if(mDataProvider->capabilities()&QgsVectorDataProvider::DeleteAttributes)
{
//delete attributes in all not commited features
for (QgsFeatureList::iterator iter = mAddedFeatures.begin(); iter != mAddedFeatures.end(); ++iter)
{
for (QgsAttributeIds::const_iterator it = deleted.begin(); it != deleted.end(); ++it)
{
(*iter).deleteAttribute(*it);
}
}
//and then in the provider
if(!mDataProvider->deleteAttributes(deleted))
{
returnvalue=false;
}
}
if(mDataProvider->capabilities()&QgsVectorDataProvider::AddAttributes)
{
//add attributes in all not commited features
// TODO: is it necessary? [MD]
/*for (QgsFeatureList::iterator iter = mAddedFeatures.begin(); iter != mAddedFeatures.end(); ++iter)
{
for (QgsNewAttributesMap::const_iterator it = added.begin(); it != added.end(); ++it)
{
(*iter).addAttribute(, QgsFeatureAttribute(it.key(), ""));
}
}*/
//and then in the provider
if(!mDataProvider->addAttributes(added))
{
returnvalue=false;
}
}
if(mDataProvider->capabilities()&QgsVectorDataProvider::ChangeAttributeValues)
{
//and then those of the commited ones
if(!mDataProvider->changeAttributeValues(changed))
{
returnvalue=false;
}
}
return returnvalue;
}
// Convenience function to transform the given point
inline void QgsVectorLayer::transformPoint(double& x,
double& y,
QgsMapToPixel* mtp,
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,
QgsMapToPixel* mtp, 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;
}
QgsFeatureList& QgsVectorLayer::addedFeatures()
{
return mAddedFeatures;
}
QgsFeatureIds& QgsVectorLayer::deletedFeatureIds()
{
return mDeletedFeatureIds;
}
QgsChangedAttributesMap& QgsVectorLayer::changedAttributes()
{
return mChangedAttributes;
}
void QgsVectorLayer::setModified(bool modified, bool onlyGeometry)
{
mModified = modified;
emit wasModified(onlyGeometry);
}