QGIS/src/core/qgsvectorlayer.cpp

4624 lines
126 KiB
C++

/***************************************************************************
qgsvectorlayer.cpp
--------------------
begin : Oct 29, 2003
copyright : (C) 2003 by Gary E.Sherman
email : sherman at mrcc.com
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.
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#include <limits>
#include <QImage>
#include <QPainter>
#include <QPainterPath>
#include <QPolygonF>
#include <QProgressDialog>
#include <QSettings>
#include <QString>
#include <QDomNode>
#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 "qgsfeaturerequest.h"
#include "qgsfield.h"
#include "qgsgeometry.h"
#include "qgslabel.h"
#include "qgslogger.h"
#include "qgsmessagelog.h"
#include "qgsmaptopixel.h"
#include "qgspoint.h"
#include "qgsproviderregistry.h"
#include "qgsrectangle.h"
#include "qgsrendercontext.h"
#include "qgscoordinatereferencesystem.h"
#include "qgsvectordataprovider.h"
#include "qgsvectorlayercache.h"
#include "qgsvectorlayereditbuffer.h"
#include "qgsvectorlayereditutils.h"
#include "qgsvectorlayerfeatureiterator.h"
#include "qgsvectorlayerjoinbuffer.h"
#include "qgsvectorlayerundocommand.h"
#include "qgsvectoroverlay.h"
#include "qgsmaplayerregistry.h"
#include "qgsclipper.h"
#include "qgsproject.h"
#include "qgsrendererv2.h"
#include "qgssymbolv2.h"
#include "qgssymbollayerv2.h"
#include "qgssinglesymbolrendererv2.h"
#include "qgsdiagramrendererv2.h"
#include "qgsstylev2.h"
#ifdef TESTPROVIDERLIB
#include <dlfcn.h>
#endif
QgsVectorLayer::QgsVectorLayer( QString vectorLayerPath,
QString baseName,
QString providerKey,
bool loadDefaultStyleFlag )
: QgsMapLayer( VectorLayer, baseName, vectorLayerPath )
, mUpdateThreshold( 0 ) // XXX better default value?
, mDataProvider( NULL )
, mProviderKey( providerKey )
, mReadOnly( false )
, mRenderer( 0 )
, mRendererV2( NULL )
, mUsingRendererV2( false )
, mLabel( 0 )
, mLabelOn( false )
, mVertexMarkerOnlyForSelection( false )
, mCache( new QgsVectorLayerCache(this) )
, mEditBuffer( 0 )
, mJoinBuffer( 0 )
, mDiagramRenderer( 0 )
, mDiagramLayerSettings( 0 )
, mValidExtent( false )
, mSymbolFeatureCounted( false )
{
mActions = new QgsAttributeAction( this );
// if we're given a provider type, try to create and bind one to this layer
if ( ! mProviderKey.isEmpty() )
{
setDataProvider( mProviderKey );
}
if ( mValid )
{
// Always set crs
setCoordinateSystem();
mJoinBuffer = new QgsVectorLayerJoinBuffer();
updateFields();
QSettings settings;
//Changed to default to true as of QGIS 1.7
//TODO: remove hack when http://hub.qgis.org/issues/5170 is fixed
#ifdef ANDROID
bool use_symbology_ng_default = false;
#else
bool use_symbology_ng_default = true;
#endif
if ( settings.value( "/qgis/use_symbology_ng", use_symbology_ng_default ).toBool() && hasGeometryType() )
{
// using symbology-ng!
setUsingRendererV2( true );
}
// check if there is a default style / propertysheet defined
// for this layer and if so apply it
bool defaultLoadedFlag = false;
if ( loadDefaultStyleFlag )
{
loadDefaultStyle( defaultLoadedFlag );
}
// if the default style failed to load or was disabled use some very basic defaults
if ( !defaultLoadedFlag && hasGeometryType() )
{
// add single symbol renderer
if ( mUsingRendererV2 )
{
setRendererV2( QgsFeatureRendererV2::defaultRenderer( geometryType() ) );
}
else
{
QgsSingleSymbolRenderer *renderer = new QgsSingleSymbolRenderer( geometryType() );
setRenderer( renderer );
}
}
connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWillBeRemoved( QString ) ), this, SLOT( checkJoinLayerRemove( QString ) ) );
// 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( "entered." );
emit layerDeleted();
mValid = false;
delete mRenderer;
delete mDataProvider;
delete mEditBuffer;
delete mJoinBuffer;
delete mCache;
delete mLabel;
delete mDiagramLayerSettings;
delete mActions;
//delete remaining overlays
QList<QgsVectorOverlay*>::iterator overlayIt = mOverlays.begin();
for ( ; overlayIt != mOverlays.end(); ++overlayIt )
{
delete *overlayIt;
}
}
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 ( !hasGeometryType() )
return;
// 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 QgsFields &fields = pendingFields();
int fieldsSize = fields.size();
for ( int idx = 0; idx < fields.count(); ++idx )
{
QString fldName = fields[idx].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();
}
}
}
}
// 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( QgsRenderContext& rendererContext )
{
if ( !hasGeometryType() )
return;
QgsDebugMsg( "Starting draw of labels: " + id() );
if (( mRenderer || mRendererV2 ) && mLabelOn &&
( !mLabel->scaleBasedVisibility() ||
( mLabel->minScale() <= rendererContext.rendererScale() &&
rendererContext.rendererScale() <= mLabel->maxScale() ) ) )
{
QgsAttributeList attributes;
if ( mRenderer )
{
attributes = mRenderer->classificationAttributes();
}
else if ( mRendererV2 )
{
foreach ( QString attrName, mRendererV2->usedAttributes() )
{
int attrNum = fieldNameIndex( attrName );
attributes.append( attrNum );
}
// make sure the renderer is ready for classification ("symbolForFeature")
mRendererV2->startRender( rendererContext, this );
}
// 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, rendererContext.extent() );
QgsFeature fet;
while ( nextFeature( fet ) )
{
if (( mRenderer && mRenderer->willRenderFeature( &fet ) )
|| ( mRendererV2 && mRendererV2->willRenderFeature( fet ) ) )
{
bool sel = mSelectedFeatureIds.contains( fet.id() );
mLabel->renderLabel( rendererContext, fet, sel, 0 );
}
featureCount++;
}
}
catch ( QgsCsException &e )
{
Q_UNUSED( e );
QgsDebugMsg( "Error projecting label locations" );
}
if ( mRendererV2 )
{
mRendererV2->stopRender( rendererContext );
}
QgsDebugMsg( QString( "Total features processed %1" ).arg( 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();
}
}
unsigned char *QgsVectorLayer::drawLineString( unsigned char *feature, QgsRenderContext &renderContext )
{
QPainter *p = renderContext.painter();
unsigned char *ptr = feature + 5;
unsigned int wkbType = *(( int* )( feature + 1 ) );
int nPoints = *(( int* )ptr );
ptr = feature + 9;
bool hasZValue = ( wkbType == QGis::WKBLineString25D );
QVector<double> x( nPoints );
QVector<double> y( nPoints );
QVector<double> z( nPoints, 0.0 );
// Extract the points from the WKB format into the x and y vectors.
for ( 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, renderContext );
// Work around a +/- 32768 limitation on coordinates
// 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 ( int i = 0; i < nPoints; ++i )
{
if ( qAbs( x.at( i ) ) > QgsClipper::MAX_X ||
qAbs( y.at( i ) ) > QgsClipper::MAX_Y )
{
QgsClipper::trimFeature( x, y, true ); // true = polyline
nPoints = x.size(); // trimming may change nPoints.
break;
}
}
// set up QPolygonF class with transformed points
QPolygonF pa( nPoints );
for ( int i = 0; i < nPoints; ++i )
{
pa[i].setX( x.at( i ) );
pa[i].setY( y.at( i ) );
}
// 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 ( mEditBuffer && renderContext.drawEditingInformation() )
{
QVector<double>::const_iterator xIt;
QVector<double>::const_iterator yIt;
for ( xIt = x.begin(), yIt = y.begin(); xIt != x.end(); ++xIt, ++yIt )
{
drawVertexMarker( *xIt, *yIt, *p, mCurrentVertexMarkerType, mCurrentVertexMarkerSize );
}
}
//restore the pen
p->setPen( pen );
return ptr;
}
unsigned char *QgsVectorLayer::drawPolygon( unsigned char *feature, QgsRenderContext &renderContext )
{
QPainter *p = renderContext.painter();
typedef QPair<QVector<double>, QVector<double> > ringType;
typedef ringType* ringTypePtr;
typedef QVector<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++ )
{
int nPoints = *(( int* )ptr );
ringTypePtr ring = new ringType( QVector<double>( nPoints ), QVector<double>( nPoints ) );
ptr += 4;
// create a dummy vector for the z coordinate
QVector<double> zVector( nPoints, 0.0 );
// Extract the points from the WKB and store in a pair of
// vectors.
for ( 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, renderContext );
// Work around a +/- 32768 limitation on coordinates
// 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 ( int i = 0; i < nPoints; ++i )
{
if ( qAbs( ring->first[i] ) > QgsClipper::MAX_X ||
qAbs( ring->second[i] ) > QgsClipper::MAX_Y )
{
QgsClipper::trimFeature( ring->first, ring->second, false );
break;
}
}
// 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 )
{
//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 );
if ( numRings == 1 )
{
ringTypePtr r = rings[0];
unsigned ringSize = r->first.size();
QPolygonF pa( ringSize );
for ( register unsigned int j = 0; j != ringSize; ++j )
{
pa[j].setX( r->first[j] );
pa[j].setY( r->second[j] );
}
p->drawPolygon( pa );
// draw vertex markers if in editing mode, but only to the main canvas
if ( mEditBuffer && renderContext.drawEditingInformation() )
{
for ( register unsigned int j = 0; j != ringSize; ++j )
{
drawVertexMarker( r->first[j], r->second[j], *p, mCurrentVertexMarkerType, mCurrentVertexMarkerSize );
}
}
delete rings[0];
}
else
{
// 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];
}
#if 0
// 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 = qMax( largestX, pa.point( i ).x() );
smallestX = qMin( smallestX, pa.point( i ).x() );
largestY = qMax( largestY, pa.point( i ).y() );
smallestY = qMin( smallestY, pa.point( i ).y() );
}
QgsDebugMsg( QString( "Largest X coordinate was %1" ).arg( largestX ) );
QgsDebugMsg( QString( "Smallest X coordinate was %1" ).arg( smallestX ) );
QgsDebugMsg( QString( "Largest Y coordinate was %1" ).arg( largestY ) );
QgsDebugMsg( QString( "Smallest Y coordinate was %1" ).arg( smallestY ) );
#endif
//
// draw the polygon
//
p->drawPath( path );
// draw vertex markers if in editing mode, but only to the main canvas
if ( mEditBuffer && renderContext.drawEditingInformation() )
{
for ( int i = 0; i < path.elementCount(); ++i )
{
const QPainterPath::Element & e = path.elementAt( i );
drawVertexMarker( e.x, e.y, *p, mCurrentVertexMarkerType, mCurrentVertexMarkerSize );
}
}
}
//
//restore brush and pen to original
//
p->setBrush( brush );
p->setPen( pen );
} // totalPoints > 0
return ptr;
}
void QgsVectorLayer::drawRendererV2( QgsRenderContext& rendererContext, bool labeling )
{
if ( !hasGeometryType() )
return;
QSettings settings;
bool vertexMarkerOnlyForSelection = settings.value( "/qgis/digitizing/marker_only_for_selected", false ).toBool();
mRendererV2->startRender( rendererContext, this );
#ifndef Q_WS_MAC
int featureCount = 0;
#endif //Q_WS_MAC
QgsFeature fet;
while ( nextFeature( fet ) )
{
try
{
if ( !fet.geometry() )
continue; // skip features without geometry
if ( rendererContext.renderingStopped() )
{
break;
}
#ifndef Q_WS_MAC //MH: disable this on Mac for now to avoid problems with resizing
#ifdef Q_WS_X11
if ( !mEnableBackbuffer ) // do not handle events, as we're already inside a paint event
{
#endif // Q_WS_X11
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();
}
#ifdef Q_WS_X11
}
#endif // Q_WS_X11
#endif // Q_WS_MAC
bool sel = mSelectedFeatureIds.contains( fet.id() );
bool drawMarker = ( mEditBuffer && ( !vertexMarkerOnlyForSelection || sel ) );
// render feature
bool rendered = mRendererV2->renderFeature( fet, rendererContext, -1, sel, drawMarker );
if ( mEditBuffer )
{
// Cache this for the use of (e.g.) modifying the feature's uncommitted geometry.
mCache->cacheGeometry( fet.id(), *fet.geometry() );
}
// labeling - register feature
if ( rendered && rendererContext.labelingEngine() )
{
if ( labeling )
{
rendererContext.labelingEngine()->registerFeature( this, fet, rendererContext );
}
if ( mDiagramRenderer )
{
rendererContext.labelingEngine()->registerDiagramFeature( this, fet, rendererContext );
}
}
}
catch ( const QgsCsException &cse )
{
Q_UNUSED( cse );
QgsDebugMsg( QString( "Failed to transform a point while drawing a feature with ID '%1'. Ignoring this feature. %2" )
.arg( fet.id() ).arg( cse.what() ) );
}
#ifndef Q_WS_MAC
++featureCount;
#endif //Q_WS_MAC
}
stopRendererV2( rendererContext, NULL );
#ifndef Q_WS_MAC
QgsDebugMsg( QString( "Total features processed %1" ).arg( featureCount ) );
#endif
}
void QgsVectorLayer::drawRendererV2Levels( QgsRenderContext& rendererContext, bool labeling )
{
if ( !hasGeometryType() )
return;
QHash< QgsSymbolV2*, QList<QgsFeature> > features; // key = symbol, value = array of features
QSettings settings;
bool vertexMarkerOnlyForSelection = settings.value( "/qgis/digitizing/marker_only_for_selected", false ).toBool();
// startRender must be called before symbolForFeature() calls to make sure renderer is ready
mRendererV2->startRender( rendererContext, this );
QgsSingleSymbolRendererV2* selRenderer = NULL;
if ( !mSelectedFeatureIds.isEmpty() )
{
selRenderer = new QgsSingleSymbolRendererV2( QgsSymbolV2::defaultSymbol( geometryType() ) );
selRenderer->symbol()->setColor( QgsRenderer::selectionColor() );
selRenderer->setVertexMarkerAppearance( currentVertexMarkerType(), currentVertexMarkerSize() );
selRenderer->startRender( rendererContext, this );
}
// 1. fetch features
QgsFeature fet;
#ifndef Q_WS_MAC
int featureCount = 0;
#endif //Q_WS_MAC
while ( nextFeature( fet ) )
{
if ( !fet.geometry() )
continue; // skip features without geometry
if ( rendererContext.renderingStopped() )
{
stopRendererV2( rendererContext, selRenderer );
return;
}
#ifndef Q_WS_MAC
if ( featureCount % 1000 == 0 )
{
qApp->processEvents();
}
#endif //Q_WS_MAC
QgsSymbolV2* sym = mRendererV2->symbolForFeature( fet );
if ( !sym )
{
continue;
}
if ( !features.contains( sym ) )
{
features.insert( sym, QList<QgsFeature>() );
}
features[sym].append( fet );
if ( mEditBuffer )
{
// Cache this for the use of (e.g.) modifying the feature's uncommitted geometry.
mCache->cacheGeometry( fet.id(), *fet.geometry() );
}
if ( sym && rendererContext.labelingEngine() )
{
if ( labeling )
{
rendererContext.labelingEngine()->registerFeature( this, fet, rendererContext );
}
if ( mDiagramRenderer )
{
rendererContext.labelingEngine()->registerDiagramFeature( this, fet, rendererContext );
}
}
#ifndef Q_WS_MAC
++featureCount;
#endif //Q_WS_MAC
}
// find out the order
QgsSymbolV2LevelOrder levels;
QgsSymbolV2List symbols = mRendererV2->symbols();
for ( int i = 0; i < symbols.count(); i++ )
{
QgsSymbolV2* sym = symbols[i];
for ( int j = 0; j < sym->symbolLayerCount(); j++ )
{
int level = sym->symbolLayer( j )->renderingPass();
if ( level < 0 || level >= 1000 ) // ignore invalid levels
continue;
QgsSymbolV2LevelItem item( sym, j );
while ( level >= levels.count() ) // append new empty levels
levels.append( QgsSymbolV2Level() );
levels[level].append( item );
}
}
// 2. draw features in correct order
for ( int l = 0; l < levels.count(); l++ )
{
QgsSymbolV2Level& level = levels[l];
for ( int i = 0; i < level.count(); i++ )
{
QgsSymbolV2LevelItem& item = level[i];
if ( !features.contains( item.symbol() ) )
{
QgsDebugMsg( "level item's symbol not found!" );
continue;
}
int layer = item.layer();
QList<QgsFeature>& lst = features[item.symbol()];
QList<QgsFeature>::iterator fit;
#ifndef Q_WS_MAC
featureCount = 0;
#endif //Q_WS_MAC
for ( fit = lst.begin(); fit != lst.end(); ++fit )
{
if ( rendererContext.renderingStopped() )
{
stopRendererV2( rendererContext, selRenderer );
return;
}
#ifndef Q_WS_MAC
if ( featureCount % 1000 == 0 )
{
qApp->processEvents();
}
#endif //Q_WS_MAC
bool sel = mSelectedFeatureIds.contains( fit->id() );
// maybe vertex markers should be drawn only during the last pass...
bool drawMarker = ( mEditBuffer && ( !vertexMarkerOnlyForSelection || sel ) );
try
{
mRendererV2->renderFeature( *fit, rendererContext, layer, sel, drawMarker );
}
catch ( const QgsCsException &cse )
{
Q_UNUSED( cse );
QgsDebugMsg( QString( "Failed to transform a point while drawing a feature with ID '%1'. Ignoring this feature. %2" )
.arg( fet.id() ).arg( cse.what() ) );
}
#ifndef Q_WS_MAC
++featureCount;
#endif //Q_WS_MAC
}
}
}
stopRendererV2( rendererContext, selRenderer );
}
void QgsVectorLayer::reload()
{
if ( mDataProvider )
{
mDataProvider->reloadData();
}
}
bool QgsVectorLayer::draw( QgsRenderContext& rendererContext )
{
if ( !hasGeometryType() )
return true;
//set update threshold before each draw to make sure the current setting is picked up
QSettings settings;
mUpdateThreshold = settings.value( "Map/updateThreshold", 0 ).toInt();
#ifdef Q_WS_X11
mEnableBackbuffer = settings.value( "/Map/enableBackbuffer", 1 ).toBool();
#endif
if ( mUsingRendererV2 )
{
if ( !mRendererV2 )
return false;
QgsDebugMsg( "rendering v2:\n" + mRendererV2->dump() );
if ( mEditBuffer )
{
// Destroy all cached geometries and clear the references to them
mCache->deleteCachedGeometries();
mCache->setCachedGeometriesRect( rendererContext.extent() );
// set editing vertex markers style
mRendererV2->setVertexMarkerAppearance( currentVertexMarkerType(), currentVertexMarkerSize() );
}
QgsAttributeList attributes;
foreach ( QString attrName, mRendererV2->usedAttributes() )
{
int attrNum = fieldNameIndex( attrName );
attributes.append( attrNum );
QgsDebugMsg( "attrs: " + attrName + " - " + QString::number( attrNum ) );
}
bool labeling = false;
//register label and diagram layer to the labeling engine
prepareLabelingAndDiagrams( rendererContext, attributes, labeling );
select( attributes, rendererContext.extent() );
if (( mRendererV2->capabilities() & QgsFeatureRendererV2::SymbolLevels )
&& mRendererV2->usingSymbolLevels() )
drawRendererV2Levels( rendererContext, labeling );
else
drawRendererV2( rendererContext, labeling );
return true;
}
//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;
//vertex marker type for selection
QgsVectorLayer::VertexMarkerType vertexMarker = QgsVectorLayer::NoMarker;
int vertexMarkerSize = 7;
if ( mEditBuffer )
{
// Destroy all cached geometries and clear the references to them
mCache->deleteCachedGeometries();
mCache->setCachedGeometriesRect( rendererContext.extent() );
vertexMarker = currentVertexMarkerType();
vertexMarkerSize = currentVertexMarkerSize();
mVertexMarkerOnlyForSelection = settings.value( "/qgis/digitizing/marker_only_for_selected", false ).toBool();
}
// int totalFeatures = pendingFeatureCount();
int featureCount = 0;
QgsFeature fet;
QgsAttributeList attributes = mRenderer->classificationAttributes();
bool labeling = false;
prepareLabelingAndDiagrams( rendererContext, attributes, labeling );
select( attributes, rendererContext.extent() );
try
{
while ( nextFeature( fet ) )
{
if ( !fet.geometry() )
continue; // skip features without geometry
if ( rendererContext.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();
}
// #else
// Q_UNUSED( totalFeatures );
#endif //Q_WS_MAC
// 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.id() );
mCurrentVertexMarkerType = QgsVectorLayer::NoMarker;
mCurrentVertexMarkerSize = 7;
if ( mEditBuffer )
{
// Cache this for the use of (e.g.) modifying the feature's uncommitted geometry.
mCache->cacheGeometry( fet.id(), *fet.geometry() );
if ( !mVertexMarkerOnlyForSelection || sel )
{
mCurrentVertexMarkerType = vertexMarker;
mCurrentVertexMarkerSize = vertexMarkerSize;
}
}
//QgsDebugMsg(QString("markerScale before renderFeature(): %1").arg(markerScaleFactor));
// markerScalerFactore reflects the wanted scaling of the marker
double opacity = 1.0;
if ( !mRenderer->usesTransparency() )
{
opacity = ( mTransparencyLevel * 1.0 ) / 255.0;
}
mRenderer->renderFeature( rendererContext, fet, &marker, sel, opacity );
// markerScalerFactore now reflects the actual scaling of the marker that the render performed.
//QgsDebugMsg(QString("markerScale after renderFeature(): %1").arg(markerScaleFactor));
//double scale = rendererContext.scaleFactor() / markerScaleFactor;
drawFeature( rendererContext, fet, &marker );
if ( mRenderer->willRenderFeature( &fet ) && rendererContext.labelingEngine() )
{
if ( labeling )
{
rendererContext.labelingEngine()->registerFeature( this, fet, rendererContext );
}
if ( mDiagramRenderer )
{
rendererContext.labelingEngine()->registerDiagramFeature( this, fet, rendererContext );
}
}
++featureCount;
}
}
catch ( QgsCsException &cse )
{
Q_UNUSED( cse );
QgsDebugMsg( QString( "Failed to transform a point while drawing a feature with ID '%1'. Rendering stopped. %2" )
.arg( fet.id() ).arg( cse.what() ) );
return false;
}
QgsDebugMsg( QString( "Total features processed %1" ).arg( featureCount ) );
}
else
{
QgsDebugMsg( "QgsRenderer is null" );
}
if ( mEditBuffer )
{
QgsDebugMsg( QString( "Cached %1 geometries." ).arg( mCache->cachedGeometries().count() ) );
}
return true; // Assume success always
}
void QgsVectorLayer::drawVertexMarker( double x, double y, QPainter& p, QgsVectorLayer::VertexMarkerType type, int m )
{
if ( type == QgsVectorLayer::SemiTransparentCircle )
{
p.setPen( QColor( 50, 100, 120, 200 ) );
p.setBrush( QColor( 200, 200, 210, 120 ) );
p.drawEllipse( x - m, y - m, m * 2 + 1, m * 2 + 1 );
}
else if ( type == QgsVectorLayer::Cross )
{
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( QgsFeatureId fid, bool emitSignal )
{
mSelectedFeatureIds.insert( fid );
if ( emitSignal )
{
// invalidate cache
setCacheImage( 0 );
emit selectionChanged();
}
}
void QgsVectorLayer::deselect( QgsFeatureId fid, bool emitSignal )
{
mSelectedFeatureIds.remove( fid );
if ( emitSignal )
{
// invalidate cache
setCacheImage( 0 );
emit selectionChanged();
}
}
void QgsVectorLayer::select( QgsRectangle & rect, bool lock )
{
// normalize the rectangle
rect.normalize();
if ( !lock )
{
removeSelection( false ); // don't emit signal
}
//select all the elements
select( QgsAttributeList(), rect, false, true );
QgsFeature f;
while ( nextFeature( f ) )
{
select( f.id(), false ); // don't emit signal (not to redraw it everytime)
}
// invalidate cache
setCacheImage( 0 );
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(), QgsRectangle(), false );
QgsFeature fet;
while ( nextFeature( fet ) )
{
select( fet.id(), false ); // don't emit signal
}
for ( QgsFeatureIds::iterator iter = tmp.begin(); iter != tmp.end(); ++iter )
{
mSelectedFeatureIds.remove( *iter );
}
// invalidate cache
setCacheImage( 0 );
emit selectionChanged();
}
void QgsVectorLayer::invertSelectionInRectangle( QgsRectangle & rect )
{
// normalize the rectangle
rect.normalize();
select( QgsAttributeList(), rect, false, true );
QgsFeature fet;
while ( nextFeature( fet ) )
{
if ( mSelectedFeatureIds.contains( fet.id() ) )
{
deselect( fet.id(), false ); // don't emit signal
}
else
{
select( fet.id(), false ); // don't emit signal
}
}
// invalidate cache
setCacheImage( 0 );
emit selectionChanged();
}
void QgsVectorLayer::removeSelection( bool emitSignal )
{
if ( mSelectedFeatureIds.size() == 0 )
return;
mSelectedFeatureIds.clear();
if ( emitSignal )
{
// invalidate cache
setCacheImage( 0 );
emit selectionChanged();
}
}
void QgsVectorLayer::triggerRepaint()
{
emit repaintRequested();
}
QgsVectorDataProvider* QgsVectorLayer::dataProvider()
{
return mDataProvider;
}
const QgsVectorDataProvider* QgsVectorLayer::dataProvider() 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 ( !hasGeometryType() )
return;
if ( r != mRenderer )
{
if ( r )
setUsingRendererV2( false );
delete mRenderer;
mRenderer = r;
}
}
void QgsVectorLayer::setDiagramRenderer( QgsDiagramRendererV2* r )
{
delete mDiagramRenderer;
mDiagramRenderer = r;
}
QGis::GeometryType QgsVectorLayer::geometryType() 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;
case QGis::WKBNoGeometry:
return QGis::NoGeometry;
}
QgsDebugMsg( QString( "Data Provider Geometry type is not recognised, is %1" ).arg( type ) );
}
else
{
QgsDebugMsg( "pointer to mDataProvider is null" );
}
// We shouldn't get here, and if we have, other things are likely to
// go wrong. Code that uses the type() 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.
QgsDebugMsg( "WARNING: This code should never be reached. Problems may occur..." );
return QGis::UnknownGeometry;
}
bool QgsVectorLayer::hasGeometryType() const
{
QGis::GeometryType t = geometryType();
return ( t != QGis::NoGeometry && t != QGis::UnknownGeometry );
}
QGis::WkbType QgsVectorLayer::wkbType() const
{
return ( QGis::WkbType )( mWkbType );
}
QgsRectangle QgsVectorLayer::boundingBoxOfSelected()
{
if ( mSelectedFeatureIds.size() == 0 ) //no selected features
{
return QgsRectangle( 0, 0, 0, 0 );
}
QgsRectangle r, retval;
retval.setMinimal();
QgsFeature fet;
if ( mDataProvider->capabilities() & QgsVectorDataProvider::SelectAtId )
{
foreach ( QgsFeatureId fid, mSelectedFeatureIds )
{
if ( featureAtId( fid, fet, true, false ) && fet.geometry() )
{
r = fet.geometry()->boundingBox();
retval.combineExtentWith( &r );
}
}
}
else
{
select( QgsAttributeList(), QgsRectangle(), true );
while ( nextFeature( fet ) )
{
if ( mSelectedFeatureIds.contains( fet.id() ) )
{
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.xMinimum() == 0.0 && retval.xMaximum() == 0.0 &&
retval.yMinimum() == 0.0 && retval.yMaximum() == 0.0 )
{
retval.set( -1.0, -1.0, 1.0, 1.0 );
}
}
return retval;
}
long QgsVectorLayer::featureCount() const
{
if ( !mDataProvider )
{
QgsDebugMsg( "invoked with null mDataProvider" );
return 0;
}
return mDataProvider->featureCount();
}
long QgsVectorLayer::featureCount( QgsSymbolV2* symbol )
{
if ( !mSymbolFeatureCounted ) return -1;
return mSymbolFeatureCountMap.value( symbol );
}
bool QgsVectorLayer::countSymbolFeatures( bool showProgress )
{
if ( mSymbolFeatureCounted ) return true;
mSymbolFeatureCountMap.clear();
if ( !mDataProvider )
{
QgsDebugMsg( "invoked with null mDataProvider" );
return false;
}
if ( !mRendererV2 )
{
QgsDebugMsg( "invoked with null mRendererV2" );
return false;
}
QgsLegendSymbolList symbolList = mRendererV2->legendSymbolItems();
QgsLegendSymbolList::const_iterator symbolIt = symbolList.constBegin();
for ( ; symbolIt != symbolList.constEnd(); ++symbolIt )
{
mSymbolFeatureCountMap.insert( symbolIt->second, 0 );
}
long nFeatures = pendingFeatureCount();
QProgressDialog progressDialog( tr( "Updating feature count for layer %1" ).arg( name() ), tr( "Abort" ), 0, nFeatures );
progressDialog.setWindowModality( Qt::WindowModal );
int featuresCounted = 0;
select( pendingAllAttributesList(), QgsRectangle(), false, false );
QgsFeature f;
// Renderer (rule based) may depend on context scale, with scale is ignored if 0
QgsRenderContext renderContext;
renderContext.setRendererScale( 0 );
mRendererV2->startRender( renderContext, this );
while ( nextFeature( f ) )
{
QgsSymbolV2List featureSymbolList = mRendererV2->symbolsForFeature( f );
for ( QgsSymbolV2List::iterator symbolIt = featureSymbolList.begin(); symbolIt != featureSymbolList.end(); ++symbolIt )
{
mSymbolFeatureCountMap[*symbolIt] += 1;
}
++featuresCounted;
if ( showProgress )
{
if ( featuresCounted % 50 == 0 )
{
if ( featuresCounted > nFeatures ) //sometimes the feature count is not correct
{
progressDialog.setMaximum( 0 );
}
progressDialog.setValue( featuresCounted );
if ( progressDialog.wasCanceled() )
{
mSymbolFeatureCountMap.clear();
mRendererV2->stopRender( renderContext );
return false;
}
}
}
}
mRendererV2->stopRender( renderContext );
progressDialog.setValue( nFeatures );
mSymbolFeatureCounted = true;
return true;
}
long QgsVectorLayer::updateFeatureCount() const
{
return -1;
}
void QgsVectorLayer::updateExtents()
{
mValidExtent = false;
}
void QgsVectorLayer::setExtent( const QgsRectangle &r )
{
QgsMapLayer::setExtent( r );
mValidExtent = true;
}
QgsRectangle QgsVectorLayer::extent()
{
if ( mValidExtent )
return QgsMapLayer::extent();
QgsRectangle rect;
rect.setMinimal();
if ( !hasGeometryType() )
return rect;
if ( !mDataProvider )
{
QgsDebugMsg( "invoked with null mDataProvider" );
}
if ( mEditBuffer && mEditBuffer->mDeletedFeatureIds.isEmpty() && mEditBuffer->mChangedGeometries.isEmpty() )
{
mDataProvider->updateExtents();
// get the extent of the layer from the provider
// but only when there are some features already
if ( mDataProvider->featureCount() != 0 )
{
QgsRectangle r = mDataProvider->extent();
rect.combineExtentWith( &r );
}
for ( QgsFeatureMap::iterator it = mEditBuffer->mAddedFeatures.begin(); it != mEditBuffer->mAddedFeatures.end(); it++ )
{
QgsRectangle r = it->geometry()->boundingBox();
rect.combineExtentWith( &r );
}
}
else
{
select( QgsAttributeList(), QgsRectangle(), true );
QgsFeature fet;
while ( nextFeature( fet ) )
{
if ( fet.geometry() )
{
QgsRectangle bb = fet.geometry()->boundingBox();
rect.combineExtentWith( &bb );
}
}
}
if ( rect.xMinimum() > rect.xMaximum() && rect.yMinimum() > rect.yMaximum() )
{
// special case when there are no features in provider nor any added
rect = QgsRectangle(); // use rectangle with zero coordinates
}
setExtent( rect );
// Send this (hopefully) up the chain to the map canvas
emit recalculateExtents();
return rect;
}
QString QgsVectorLayer::subsetString()
{
if ( ! mDataProvider )
{
QgsDebugMsg( "invoked with null mDataProvider" );
return 0;
}
return mDataProvider->subsetString();
}
bool QgsVectorLayer::setSubsetString( QString subset )
{
if ( ! mDataProvider )
{
QgsDebugMsg( "invoked with null mDataProvider" );
return false;
}
bool res = mDataProvider->setSubsetString( subset );
// get the updated data source string from the provider
mDataSource = mDataProvider->dataSourceUri();
updateExtents();
if ( res )
setCacheImage( 0 );
return res;
}
#if 0
void QgsVectorLayer::addJoinedAttributes( QgsFeature& f, bool all )
{
if ( all || ( mFetchAttributes.size() > 0 && mJoinBuffer->containsFetchJoins() ) )
{
mJoinBuffer->updateFeatureAttributes( f, f.attributes().count(), all );
}
}
#endif
void QgsVectorLayer::select( QgsAttributeList attributes, QgsRectangle rect, bool fetchGeometries, bool useIntersect )
{
#if 0
if ( !mDataProvider )
return;
mFetching = true;
mFetchRect = rect;
mFetchAttributes = attributes;
mFetchGeometry = fetchGeometries;
mFetchConsidered = mEditBuffer ? mEditBuffer->mDeletedFeatureIds : QSet<QgsFeatureId>();
QgsAttributeList targetJoinFieldList;
if ( mEditBuffer )
{
mFetchAddedFeaturesIt = mEditBuffer->mAddedFeatures.begin();
mFetchChangedGeomIt = mEditBuffer->mChangedGeometries.begin();
}
//look in the normal features of the provider
if ( mFetchAttributes.size() > 0 )
{
if ( mEditBuffer || mJoinBuffer->containsJoins() )
{
QgsAttributeList joinFields;
mJoinBuffer->select( mFetchAttributes, joinFields, mUpdatedFields );
QgsAttributeList::const_iterator joinFieldIt = joinFields.constBegin();
for ( ; joinFieldIt != joinFields.constEnd(); ++joinFieldIt )
{
if ( !mFetchAttributes.contains( *joinFieldIt ) )
{
mFetchAttributes.append( *joinFieldIt );
}
}
//detect which fields are from the provider
mFetchProvAttributes.clear();
const QgsFields& provFields = mDataProvider->fields();
for ( QgsAttributeList::iterator it = mFetchAttributes.begin(); it != mFetchAttributes.end(); it++ )
{
if ( *it < provFields.count() )
{
mFetchProvAttributes << *it;
}
}
mProviderIterator = mDataProvider->select( mFetchProvAttributes, rect, fetchGeometries, useIntersect );
}
else
{
mProviderIterator = mDataProvider->select( mFetchAttributes, rect, fetchGeometries, useIntersect );
}
}
else //we don't need any attributes at all
{
mProviderIterator = mDataProvider->select( QgsAttributeList(), rect, fetchGeometries, useIntersect );
}
#endif
QgsFeatureRequest request;
if ( !rect.isEmpty() )
request.setFilterRect( rect );
if ( !fetchGeometries )
request.setFlags( QgsFeatureRequest::NoGeometry );
if ( useIntersect )
request.setFlags( request.flags() | QgsFeatureRequest::ExactIntersect );
if ( attributes != pendingAllAttributesList() )
request.setSubsetOfAttributes( attributes );
mLayerIterator = getFeatures( request );
}
#if 0
void QgsVectorLayer::select( const QgsFeatureRequest& request )
{
mLayerIterator = getFeatures( request );
/*
QgsAttributeList attrs;
if ( !( request.flags() & QgsFeatureRequest::SubsetOfAttributes ) )
attrs = request.subsetOfAttributes();
else
attrs = pendingAllAttributesList();
bool fetchGeom = !( request.flags() & QgsFeatureRequest::NoGeometry );
bool exactIntersect = ( request.flags() & QgsFeatureRequest::ExactIntersect );
select( attrs, request.filterRect(), fetchGeom, exactIntersect );
*/
}
#endif
QgsFeatureIterator QgsVectorLayer::getFeatures( const QgsFeatureRequest& request )
{
if ( !mDataProvider )
return QgsFeatureIterator();
return QgsFeatureIterator( new QgsVectorLayerFeatureIterator(this, request) );
}
bool QgsVectorLayer::nextFeature( QgsFeature &f )
{
return mLayerIterator.nextFeature( f );
#if 0
if ( !mFetching )
return false;
if ( mEditBuffer )
{
if ( !mFetchRect.isEmpty() )
{
// check if changed geometries are in rectangle
for ( ; mFetchChangedGeomIt != mEditBuffer->mChangedGeometries.end(); mFetchChangedGeomIt++ )
{
QgsFeatureId 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 );
f.setValid( true );
if ( mFetchGeometry )
f.setGeometry( mFetchChangedGeomIt.value() );
if ( mFetchAttributes.size() > 0 )
{
if ( fid < 0 )
{
// fid<0 => in mAddedFeatures
bool found = false;
for ( QgsFeatureList::iterator it = mEditBuffer->mAddedFeatures.begin(); it != mEditBuffer->mAddedFeatures.end(); it++ )
{
if ( fid == it->id() )
{
found = true;
f.setAttributes( it->attributes() );
// no need to update (always up-to-date) updateFeatureAttributes( f );
break;
}
}
if ( !found )
{
QgsDebugMsg( QString( "No attributes for the added feature %1 found" ).arg( f.id() ) );
}
}
else
{
// retrieve attributes from provider
QgsFeature tmp;
//mDataProvider->featureAtId( fid, tmp, false, mFetchProvAttributes );
QgsFeatureRequest request;
request.setFilterFid( fid ).setSubsetOfAttributes( mFetchProvAttributes );
QgsFeatureIterator fi = mDataProvider->getFeatures( request );
if ( fi.nextFeature( tmp ) )
{
if (mEditBuffer)
mEditBuffer->updateChangedAttributes( tmp );
f.setAttributes( tmp.attributes() );
}
}
}
addJoinedAttributes( f );
// return complete feature
mFetchChangedGeomIt++;
return true;
}
// no more changed geometries
}
for ( ; mFetchAddedFeaturesIt != mEditBuffer->mAddedFeatures.end(); mFetchAddedFeaturesIt++ )
{
QgsFeatureId fid = mFetchAddedFeaturesIt->id();
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 );
f.setValid( true );
if ( mFetchGeometry )
f.setGeometry( *mFetchAddedFeaturesIt->geometry() );
if ( mFetchAttributes.size() > 0 )
{
f.setAttributes( mFetchAddedFeaturesIt->attributes() );
// no need to do this (always up-to-date) updateFeatureAttributes( f );
}
addJoinedAttributes( f );
mFetchAddedFeaturesIt++;
return true;
}
// no more added features
}
while ( mProviderIterator.nextFeature( f ) )
{
if ( mFetchConsidered.contains( f.id() ) )
{
continue;
}
if ( mFetchAttributes.size() > 0 )
{
if (mEditBuffer)
mEditBuffer->updateChangedAttributes( f ); //check changed attributes
addJoinedAttributes( f ); // check joined attributes
}
if ( mEditBuffer && mFetchGeometry )
{
mEditBuffer->updateFeatureGeometry( f );
}
return true;
}
mFetching = false;
return false;
#endif
}
bool QgsVectorLayer::featureAtId( QgsFeatureId featureId, QgsFeature& f, bool fetchGeometries, bool fetchAttributes )
{
QgsFeatureRequest request;
request.setFilterFid( featureId );
if ( !fetchGeometries )
request.setFlags( QgsFeatureRequest::NoGeometry );
if ( !fetchAttributes )
request.setSubsetOfAttributes( QgsAttributeList() );
QgsFeatureIterator fi = getFeatures( request );
return fi.nextFeature( f );
#if 0
if ( !mDataProvider )
return false;
if ( mEditBuffer && mEditBuffer->mDeletedFeatureIds.contains( featureId ) )
return false;
if ( fetchGeometries && mEditBuffer->mChangedGeometries.contains( featureId ) )
{
f.setFeatureId( featureId );
f.setValid( true );
f.setGeometry( mEditBuffer->mChangedGeometries[featureId] );
if ( fetchAttributes )
{
if ( featureId < 0 && mEditBuffer )
{
// featureId<0 => in mAddedFeatures
bool found = false;
for ( QgsFeatureList::iterator it = mEditBuffer->mAddedFeatures.begin(); it != mEditBuffer->mAddedFeatures.end(); it++ )
{
if ( featureId != it->id() )
{
found = true;
f.setAttributes( it->attributes() );
break;
}
}
if ( !found )
{
QgsDebugMsg( QString( "No attributes for the added feature %1 found" ).arg( f.id() ) );
}
}
else
{
// retrieve attributes from provider
QgsFeature tmp;
//mDataProvider->featureAtId( featureId, tmp, false, mDataProvider->attributeIndexes() );
QgsFeatureRequest request;
request.setFilterFid( featureId ).setFlags( QgsFeatureRequest::NoGeometry );
QgsFeatureIterator fi = mDataProvider->getFeatures( request );
if ( fi.nextFeature( tmp ) )
{
f.setAttributes( tmp.attributes() );
if (mEditBuffer)
mEditBuffer->updateChangedAttributes( f );
}
}
addJoinedAttributes( f, true );
}
return true;
}
//added features
if (mEditBuffer)
{
for ( QgsFeatureList::iterator iter = mEditBuffer->mAddedFeatures.begin(); iter != mEditBuffer->mAddedFeatures.end(); ++iter )
{
if ( iter->id() == featureId )
{
f.setFeatureId( iter->id() );
f.setValid( true );
if ( fetchGeometries )
f.setGeometry( *iter->geometry() );
if ( fetchAttributes )
f.setAttributes( iter->attributes() );
return true;
}
}
}
// regular features
QgsFeatureRequest request;
request.setFilterFid( featureId );
if ( !fetchGeometries )
request.setFlags( QgsFeatureRequest::NoGeometry );
if ( !fetchAttributes )
request.setSubsetOfAttributes( QgsAttributeList() );
QgsFeatureIterator fi = mDataProvider->getFeatures( request );
if ( fi.nextFeature( f ) )
{
if (mEditBuffer)
mEditBuffer->updateChangedAttributes( f );
addJoinedAttributes( f, true );
return true;
}
return false;
#endif
}
bool QgsVectorLayer::addFeature( QgsFeature& f, bool alsoUpdateExtent )
{
if ( !mEditBuffer || !mDataProvider )
return false;
// TODO[MD]: alsoUpdateExtent
return mEditBuffer->addFeature(f);
}
bool QgsVectorLayer::updateFeature( QgsFeature &f )
{
QgsFeature current;
if ( !featureAtId( f.id(), current, f.geometry(), !f.attributes().isEmpty() ) )
{
QgsDebugMsg( QString( "feature %1 could not be retrieved" ).arg( f.id() ) );
return false;
}
if ( f.geometry() && current.geometry() && f.geometry() != current.geometry() && !f.geometry()->isGeosEqual( *current.geometry() ) )
{
if ( !changeGeometry( f.id(), f.geometry() ) )
{
QgsDebugMsg( QString( "geometry of feature %1 could not be changed." ).arg( f.id() ) );
return false;
}
}
const QgsAttributes &fa = f.attributes();
const QgsAttributes &ca = current.attributes();
for ( int attr = 0; attr < fa.count(); ++attr )
{
if ( fa[attr] != ca[attr] )
{
if ( !changeAttributeValue( f.id(), attr, fa[attr] ) )
{
QgsDebugMsg( QString( "attribute %1 of feature %2 could not be changed." ).arg( attr ).arg( f.id() ) );
return false;
}
}
}
return true;
}
bool QgsVectorLayer::insertVertex( double x, double y, QgsFeatureId atFeatureId, int beforeVertex )
{
if ( !mEditBuffer || !mDataProvider )
return false;
QgsVectorLayerEditUtils utils(this);
return utils.insertVertex( x, y, atFeatureId, beforeVertex );
}
bool QgsVectorLayer::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex )
{
if ( !mEditBuffer || !mDataProvider )
return false;
QgsVectorLayerEditUtils utils(this);
return utils.moveVertex( x, y, atFeatureId, atVertex );
}
bool QgsVectorLayer::deleteVertex( QgsFeatureId atFeatureId, int atVertex )
{
if ( !mEditBuffer || !mDataProvider )
return false;
QgsVectorLayerEditUtils utils(this);
return utils.deleteVertex( atFeatureId, atVertex );
}
bool QgsVectorLayer::deleteSelectedFeatures()
{
if ( !( mDataProvider->capabilities() & QgsVectorDataProvider::DeleteFeatures ) )
{
return false;
}
if ( !isEditable() )
{
return false;
}
if ( mSelectedFeatureIds.size() == 0 )
return true;
while ( mSelectedFeatureIds.size() > 0 )
{
QgsFeatureId fid = *mSelectedFeatureIds.begin();
deleteFeature( fid ); // removes from selection
}
// invalidate cache
setCacheImage( 0 );
emit selectionChanged();
triggerRepaint();
updateExtents();
return true;
}
int QgsVectorLayer::addRing( const QList<QgsPoint>& ring )
{
if ( !mEditBuffer || !mDataProvider )
return 6;
QgsVectorLayerEditUtils utils(this);
return utils.addRing( ring );
}
int QgsVectorLayer::addPart( const QList<QgsPoint> &points )
{
if ( !mEditBuffer || !mDataProvider )
return 7;
//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;
}
QgsVectorLayerEditUtils utils(this);
return utils.addPart( points, *mSelectedFeatureIds.constBegin() );
}
int QgsVectorLayer::translateFeature( QgsFeatureId featureId, double dx, double dy )
{
if (!mEditBuffer || !mDataProvider)
return -1;
QgsVectorLayerEditUtils utils(this);
return utils.translateFeature( featureId, dx, dy );
}
int QgsVectorLayer::splitFeatures( const QList<QgsPoint>& splitLine, bool topologicalEditing )
{
if (!mEditBuffer || !mDataProvider)
return -1;
QgsVectorLayerEditUtils utils(this);
return utils.splitFeatures( splitLine, topologicalEditing );
}
int QgsVectorLayer::removePolygonIntersections( QgsGeometry* geom, QgsFeatureIds ignoreFeatures )
{
if ( !hasGeometryType() )
return 1;
int returnValue = 0;
//first test if geom really has type polygon or multipolygon
if ( geom->type() != QGis::Polygon )
{
return 1;
}
//get bounding box of geom
QgsRectangle geomBBox = geom->boundingBox();
//get list of features that intersect this bounding box
select( QgsAttributeList(), geomBBox, true, true );
QgsFeature f;
while ( nextFeature( f ) )
{
if ( ignoreFeatures.contains( f.id() ) )
{
continue;
}
//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 (!mEditBuffer || !mDataProvider)
return -1;
QgsVectorLayerEditUtils utils(this);
return utils.addTopologicalPoints( geom );
}
int QgsVectorLayer::addTopologicalPoints( const QgsPoint& p )
{
if (!mEditBuffer || !mDataProvider)
return -1;
QgsVectorLayerEditUtils utils(this);
return utils.addTopologicalPoints( p );
}
QgsLabel *QgsVectorLayer::label()
{
return mLabel;
}
const QgsLabel *QgsVectorLayer::label() const
{
return mLabel;
}
void QgsVectorLayer::enableLabels( bool on )
{
mLabelOn = on;
}
bool QgsVectorLayer::hasLabelsEnabled( void ) const
{
return mLabelOn;
}
bool QgsVectorLayer::startEditing()
{
if ( !mDataProvider )
{
return false;
}
// allow editing if provider supports any of the capabilities
if ( !( mDataProvider->capabilities() & QgsVectorDataProvider::EditingCapabilities ) )
{
return false;
}
if ( mReadOnly )
{
return false;
}
if ( mEditBuffer )
{
// editing already underway
return false;
}
mEditBuffer = new QgsVectorLayerEditBuffer(this);
// forward signals
connect(mEditBuffer, SIGNAL(layerModified()), this, SIGNAL(layerModified())); // TODO[MD]: necessary?
connect(mEditBuffer, SIGNAL(layerModified()), this, SLOT(triggerRepaint())); // TODO[MD]: works well?
connect(mEditBuffer, SIGNAL(featureAdded(QgsFeatureId)), this, SIGNAL(featureAdded(QgsFeatureId)));
connect(mEditBuffer, SIGNAL(featureDeleted(QgsFeatureId)), this, SIGNAL(featureDeleted(QgsFeatureId)));
connect(mEditBuffer, SIGNAL(geometryChanged(QgsFeatureId,QgsGeometry&)), this, SIGNAL(geometryChanged(QgsFeatureId,QgsGeometry&)));
connect(mEditBuffer, SIGNAL(attributeValueChanged(QgsFeatureId,int,QVariant)), this, SIGNAL(attributeValueChanged(QgsFeatureId,int,QVariant)));
connect(mEditBuffer, SIGNAL(attributeAdded(int)), this, SIGNAL(attributeAdded(int)));
connect(mEditBuffer, SIGNAL(attributeDeleted(int)), this, SIGNAL(attributeDeleted(int)));
updateFields();
emit editingStarted();
return true;
}
bool QgsVectorLayer::readXml( const QDomNode& layer_node )
{
QgsDebugMsg( QString( "Datasource in QgsVectorLayer::readXml: " ) + mDataSource.toLocal8Bit().data() );
//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;
}
QDomElement pkeyElem = pkeyNode.toElement();
if ( !pkeyElem.isNull() )
{
QString encodingString = pkeyElem.attribute( "encoding" );
if ( !encodingString.isEmpty() )
{
mDataProvider->setEncoding( encodingString );
}
}
//load vector joins
if ( !mJoinBuffer )
{
mJoinBuffer = new QgsVectorLayerJoinBuffer();
}
mJoinBuffer->readXml( layer_node );
updateFields();
connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWillBeRemoved( QString ) ), this, SLOT( checkJoinLayerRemove( QString ) ) );
QString errorMsg;
if ( !readSymbology( layer_node, errorMsg ) )
{
return false;
}
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()->provider( 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
QgsRectangle mbr = mDataProvider->extent();
// show the extent
QString s = mbr.toString();
QgsDebugMsg( "Extent of layer: " + s );
// store the extent
setExtent( mbr );
// get and store the feature type
mWkbType = 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( "\"[^\"]+\"\\.\"([^\"]+)\"( \\([^)]+\\))?" );
if ( reg.indexIn( name() ) >= 0 )
{
QStringList stuff = reg.capturedTexts();
QString lName = stuff[1];
const QMap<QString, QgsMapLayer*> &layers = QgsMapLayerRegistry::instance()->mapLayers();
QMap<QString, QgsMapLayer*>::const_iterator it;
for ( it = layers.constBegin(); it != layers.constEnd() && ( *it )->name() != lName; it++ )
;
if ( it != layers.constEnd() && stuff.size() > 2 )
{
lName += "." + stuff[2].mid( 2, stuff[2].length() - 3 );
}
if ( !lName.isEmpty() )
setLayerName( lName );
}
QgsDebugMsg( "Beautified layer name " + name() );
// deal with unnecessary schema qualification to make v.in.ogr happy
mDataSource = mDataProvider->dataSourceUri();
}
else if ( mProviderKey == "osm" )
{
// make sure that the "observer" has been removed from URI to avoid crashes
mDataSource = mDataProvider->dataSourceUri();
}
else if ( provider == "ogr" )
{
// make sure that the /vsigzip or /vsizip is added to uri, if applicable
mDataSource = mDataProvider->dataSourceUri();
if ( mDataSource.right( 10 ) == "|layerid=0" )
mDataSource.chop( 10 );
}
// 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() ) )
{
QgsDebugMsg( "can't find <maplayer>" );
return false;
}
mapLayerNode.setAttribute( "type", "vector" );
// set the geometry type
mapLayerNode.setAttribute( "geometry", QGis::qgisVectorGeometryType[geometryType()] );
// add provider node
if ( mDataProvider )
{
QDomElement provider = document.createElement( "provider" );
provider.setAttribute( "encoding", mDataProvider->encoding() );
QDomText providerText = document.createTextNode( providerType() );
provider.appendChild( providerText );
layer_node.appendChild( provider );
}
//save joins
mJoinBuffer->writeXml( layer_node, document );
// renderer specific settings
QString errorMsg;
return writeSymbology( layer_node, document, errorMsg );
} // bool QgsVectorLayer::writeXml
bool QgsVectorLayer::readSymbology( const QDomNode& node, QString& errorMessage )
{
if ( hasGeometryType() )
{
// try renderer v2 first
QDomElement rendererElement = node.firstChildElement( RENDERER_TAG_NAME );
if ( !rendererElement.isNull() )
{
// using renderer v2
setUsingRendererV2( true );
QgsFeatureRendererV2* r = QgsFeatureRendererV2::load( rendererElement );
if ( !r )
return false;
setRendererV2( r );
}
else
{
// using renderer v1
setUsingRendererV2( false );
// create and bind a renderer to this layer
QDomNode singlenode = node.namedItem( "singlesymbol" );
QDomNode graduatednode = node.namedItem( "graduatedsymbol" );
QDomNode continuousnode = node.namedItem( "continuoussymbol" );
QDomNode uniquevaluenode = node.namedItem( "uniquevalue" );
QgsRenderer * renderer = 0;
int returnCode = 1;
if ( !singlenode.isNull() )
{
renderer = new QgsSingleSymbolRenderer( geometryType() );
returnCode = renderer->readXML( singlenode, *this );
}
else if ( !graduatednode.isNull() )
{
renderer = new QgsGraduatedSymbolRenderer( geometryType() );
returnCode = renderer->readXML( graduatednode, *this );
}
else if ( !continuousnode.isNull() )
{
renderer = new QgsContinuousColorRenderer( geometryType() );
returnCode = renderer->readXML( continuousnode, *this );
}
else if ( !uniquevaluenode.isNull() )
{
renderer = new QgsUniqueValueRenderer( geometryType() );
returnCode = renderer->readXML( uniquevaluenode, *this );
}
if ( !renderer )
{
errorMessage = tr( "Unknown renderer" );
return false;
}
if ( returnCode == 1 )
{
errorMessage = tr( "No renderer object" );
delete renderer;
return false;
}
else if ( returnCode == 2 )
{
errorMessage = tr( "Classification field not found" );
delete renderer;
return false;
}
mRenderer = renderer;
}
// get and set the display field if it exists.
QDomNode displayFieldNode = node.namedItem( "displayfield" );
if ( !displayFieldNode.isNull() )
{
QDomElement e = displayFieldNode.toElement();
setDisplayField( e.text() );
}
// use scale dependent visibility flag
QDomElement e = node.toElement();
mLabel->setScaleBasedVisibility( e.attribute( "scaleBasedLabelVisibilityFlag", "0" ) == "1" );
mLabel->setMinScale( e.attribute( "minLabelScale", "1" ).toFloat() );
mLabel->setMaxScale( e.attribute( "maxLabelScale", "100000000" ).toFloat() );
//also restore custom properties (for labeling-ng)
readCustomProperties( node, "labeling" );
// tab display
QDomNode editorLayoutNode = node.namedItem( "editorlayout" );
if ( editorLayoutNode.isNull() )
{
mEditorLayout = GeneratedLayout;
}
else
{
if ( editorLayoutNode.toElement().text() == "uifilelayout" )
{
mEditorLayout = UiFileLayout;
}
else if ( editorLayoutNode.toElement().text() == "tablayout" )
{
mEditorLayout = TabLayout;
}
else
{
mEditorLayout = GeneratedLayout;
}
}
// Test if labeling is on or off
QDomNode labelnode = node.namedItem( "label" );
QDomElement element = labelnode.toElement();
int hasLabelsEnabled = element.text().toInt();
if ( hasLabelsEnabled < 1 )
{
enableLabels( false );
}
else
{
enableLabels( true );
}
QDomNode labelattributesnode = node.namedItem( "labelattributes" );
if ( !labelattributesnode.isNull() )
{
QgsDebugMsg( "calling readXML" );
mLabel->readXML( labelattributesnode );
}
//diagram renderer and diagram layer settings
delete mDiagramRenderer; mDiagramRenderer = 0;
QDomElement singleCatDiagramElem = node.firstChildElement( "SingleCategoryDiagramRenderer" );
if ( !singleCatDiagramElem.isNull() )
{
mDiagramRenderer = new QgsSingleCategoryDiagramRenderer();
mDiagramRenderer->readXML( singleCatDiagramElem );
}
QDomElement linearDiagramElem = node.firstChildElement( "LinearlyInterpolatedDiagramRenderer" );
if ( !linearDiagramElem.isNull() )
{
mDiagramRenderer = new QgsLinearlyInterpolatedDiagramRenderer();
mDiagramRenderer->readXML( linearDiagramElem );
}
if ( mDiagramRenderer )
{
QDomElement diagramSettingsElem = node.firstChildElement( "DiagramLayerSettings" );
if ( !diagramSettingsElem.isNull() )
{
mDiagramLayerSettings = new QgsDiagramLayerSettings();
mDiagramLayerSettings->readXML( diagramSettingsElem );
}
}
}
// process the attribute actions
mActions->readXML( node );
mEditTypes.clear();
QDomNode editTypesNode = 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" );
if ( fieldNameIndex( name ) < -1 )
continue;
EditType editType = ( EditType ) editTypeElement.attribute( "type" ).toInt();
mEditTypes.insert( name, editType );
switch ( editType )
{
case ValueMap:
if ( 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" ) );
}
}
break;
case EditRange:
case SliderRange:
case DialRange:
{
QVariant min = editTypeElement.attribute( "min" );
QVariant max = editTypeElement.attribute( "max" );
QVariant step = editTypeElement.attribute( "step" );
mRanges[ name ] = RangeData( min, max, step );
}
break;
case CheckBox:
mCheckedStates[ name ] = QPair<QString, QString>( editTypeElement.attribute( "checked" ), editTypeElement.attribute( "unchecked" ) );
break;
case ValueRelation:
{
QString id = editTypeElement.attribute( "layer" );
QString key = editTypeElement.attribute( "key" );
QString value = editTypeElement.attribute( "value" );
bool allowNull = editTypeElement.attribute( "allowNull" ) == "true";
bool orderByValue = editTypeElement.attribute( "orderByValue" ) == "true";
bool allowMulti = editTypeElement.attribute( "allowMulti", "false" ) == "true";
QString filterAttributeColumn = editTypeElement.attribute( "filterAttributeColumn", QString::null );
QString filterAttributeValue = editTypeElement.attribute( "filterAttributeValue", QString::null );
mValueRelations[ name ] = ValueRelationData( id, key, value, allowNull, orderByValue, allowMulti, filterAttributeColumn, filterAttributeValue );
}
break;
case Classification:
case FileName:
case Immutable:
case Hidden:
case LineEdit:
case TextEdit:
case Calendar:
case Enumeration:
case UniqueValues:
case UniqueValuesEditable:
case UuidGenerator:
break;
}
}
}
QDomNode editFormNode = node.namedItem( "editform" );
if ( !editFormNode.isNull() )
{
QDomElement e = editFormNode.toElement();
mEditForm = QgsProject::instance()->readPath( e.text() );
}
QDomNode editFormInitNode = node.namedItem( "editforminit" );
if ( !editFormInitNode.isNull() )
{
mEditFormInit = editFormInitNode.toElement().text();
}
QDomNode annotationFormNode = node.namedItem( "annotationform" );
if ( !annotationFormNode.isNull() )
{
QDomElement e = annotationFormNode.toElement();
mAnnotationForm = QgsProject::instance()->readPath( e.text() );
}
mAttributeAliasMap.clear();
QDomNode aliasesNode = node.namedItem( "aliases" );
if ( !aliasesNode.isNull() )
{
QDomElement aliasElem;
QString name;
QDomNodeList aliasNodeList = aliasesNode.toElement().elementsByTagName( "alias" );
for ( int i = 0; i < aliasNodeList.size(); ++i )
{
aliasElem = aliasNodeList.at( i ).toElement();
QString field;
if ( aliasElem.hasAttribute( "field" ) )
{
field = aliasElem.attribute( "field" );
}
else
{
int index = aliasElem.attribute( "index" ).toInt();
if ( index >= 0 && index < pendingFields().count() )
field = pendingFields()[ index ].name();
}
mAttributeAliasMap.insert( field, aliasElem.attribute( "name" ) );
}
}
//Attributes excluded from WMS and WFS
mExcludeAttributesWMS.clear();
QDomNode excludeWMSNode = node.namedItem( "excludeAttributesWMS" );
if ( !excludeWMSNode.isNull() )
{
QDomNodeList attributeNodeList = excludeWMSNode.toElement().elementsByTagName( "attribute" );
for ( int i = 0; i < attributeNodeList.size(); ++i )
{
mExcludeAttributesWMS.insert( attributeNodeList.at( i ).toElement().text() );
}
}
mExcludeAttributesWFS.clear();
QDomNode excludeWFSNode = node.namedItem( "excludeAttributesWFS" );
if ( !excludeWFSNode.isNull() )
{
QDomNodeList attributeNodeList = excludeWFSNode.toElement().elementsByTagName( "attribute" );
for ( int i = 0; i < attributeNodeList.size(); ++i )
{
mExcludeAttributesWFS.insert( attributeNodeList.at( i ).toElement().text() );
}
}
// tabs and groups display info
mAttributeEditorElements.clear();
QDomNode attributeEditorFormNode = node.namedItem( "attributeEditorForm" );
QDomNodeList attributeEditorFormNodeList = attributeEditorFormNode.toElement().childNodes();
for ( int i = 0; i < attributeEditorFormNodeList.size(); i++ )
{
QDomElement elem = attributeEditorFormNodeList.at( i ).toElement();
QgsAttributeEditorElement *attributeEditorWidget = attributeEditorElementFromDomElement( elem, this );
mAttributeEditorElements.append( attributeEditorWidget );
}
return true;
}
QgsAttributeEditorElement* QgsVectorLayer::attributeEditorElementFromDomElement( QDomElement &elem, QObject* parent )
{
QgsAttributeEditorElement* newElement = NULL;
if ( elem.tagName() == "attributeEditorContainer" )
{
QgsAttributeEditorContainer* container = new QgsAttributeEditorContainer( elem.attribute( "name" ), parent );
QDomNodeList childNodeList = elem.childNodes();
for ( int i = 0; i < childNodeList.size(); i++ )
{
QDomElement childElem = childNodeList.at( i ).toElement();
QgsAttributeEditorElement* myElem = attributeEditorElementFromDomElement( childElem, container );
container->addChildElement( myElem );
}
newElement = container;
}
else if ( elem.tagName() == "attributeEditorField" )
{
newElement = new QgsAttributeEditorField( elem.attribute( "name" ), elem.attribute( "idx" ).toInt(), parent );
}
return newElement;
}
bool QgsVectorLayer::writeSymbology( QDomNode& node, QDomDocument& doc, QString& errorMessage ) const
{
QDomElement mapLayerNode = node.toElement();
if ( hasGeometryType() )
{
if ( mUsingRendererV2 )
{
QDomElement rendererElement = mRendererV2->save( doc );
node.appendChild( rendererElement );
}
else
{
// use scale dependent visibility flag
mapLayerNode.setAttribute( "scaleBasedLabelVisibilityFlag", mLabel->scaleBasedVisibility() ? 1 : 0 );
mapLayerNode.setAttribute( "minLabelScale", QString::number( mLabel->minScale() ) );
mapLayerNode.setAttribute( "maxLabelScale", QString::number( mLabel->maxScale() ) );
//classification field(s)
QgsAttributeList attributes = mRenderer->classificationAttributes();
const QgsFields& providerFields = mDataProvider->fields();
for ( QgsAttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it )
{
QDomElement classificationElement = doc.createElement( "classificationattribute" );
QDomText classificationText = doc.createTextNode( providerFields[*it].name() );
classificationElement.appendChild( classificationText );
node.appendChild( classificationElement );
}
// renderer settings
const QgsRenderer * myRenderer = renderer();
if ( myRenderer )
{
if ( !myRenderer->writeXML( node, doc, *this ) )
{
errorMessage = tr( "renderer failed to save" );
return false;
}
}
else
{
QgsDebugMsg( "no renderer" );
errorMessage = tr( "no renderer" );
return false;
}
}
//save customproperties (for labeling ng)
writeCustomProperties( node, doc );
// tab display
QDomElement editorLayoutElem = doc.createElement( "editorlayout" );
switch ( mEditorLayout )
{
case UiFileLayout:
editorLayoutElem.appendChild( doc.createTextNode( "uifilelayout" ) );
break;
case TabLayout:
editorLayoutElem.appendChild( doc.createTextNode( "tablayout" ) );
break;
case GeneratedLayout:
default:
editorLayoutElem.appendChild( doc.createTextNode( "generatedlayout" ) );
break;
}
node.appendChild( editorLayoutElem );
// add the display field
QDomElement dField = doc.createElement( "displayfield" );
QDomText dFieldText = doc.createTextNode( displayField() );
dField.appendChild( dFieldText );
node.appendChild( dField );
// add label node
QDomElement labelElem = doc.createElement( "label" );
QDomText labelText = doc.createTextNode( "" );
if ( hasLabelsEnabled() )
{
labelText.setData( "1" );
}
else
{
labelText.setData( "0" );
}
labelElem.appendChild( labelText );
node.appendChild( labelElem );
// Now we get to do all that all over again for QgsLabel
QString fieldname = mLabel->labelField( QgsLabel::Text );
if ( fieldname != "" )
{
dField = doc.createElement( "labelfield" );
dFieldText = doc.createTextNode( fieldname );
dField.appendChild( dFieldText );
node.appendChild( dField );
}
mLabel->writeXML( node, doc );
if ( mDiagramRenderer )
{
mDiagramRenderer->writeXML( mapLayerNode, doc );
if ( mDiagramLayerSettings )
mDiagramLayerSettings->writeXML( mapLayerNode, doc );
}
}
//edit types
if ( mEditTypes.size() > 0 )
{
QDomElement editTypesElement = doc.createElement( "edittypes" );
for ( QMap<QString, EditType>::const_iterator it = mEditTypes.begin(); it != mEditTypes.end(); ++it )
{
QDomElement editTypeElement = doc.createElement( "edittype" );
editTypeElement.setAttribute( "name", it.key() );
editTypeElement.setAttribute( "type", it.value() );
switch (( EditType ) it.value() )
{
case 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 = doc.createElement( "valuepair" );
value.setAttribute( "key", vmit.key() );
value.setAttribute( "value", vmit.value().toString() );
editTypeElement.appendChild( value );
}
}
break;
case EditRange:
case SliderRange:
case DialRange:
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() );
}
break;
case CheckBox:
if ( mCheckedStates.contains( it.key() ) )
{
editTypeElement.setAttribute( "checked", mCheckedStates[ it.key()].first );
editTypeElement.setAttribute( "unchecked", mCheckedStates[ it.key()].second );
}
break;
case ValueRelation:
if ( mValueRelations.contains( it.key() ) )
{
const ValueRelationData &data = mValueRelations[ it.key()];
editTypeElement.setAttribute( "layer", data.mLayer );
editTypeElement.setAttribute( "key", data.mKey );
editTypeElement.setAttribute( "value", data.mValue );
editTypeElement.setAttribute( "allowNull", data.mAllowNull ? "true" : "false" );
editTypeElement.setAttribute( "orderByValue", data.mOrderByValue ? "true" : "false" );
editTypeElement.setAttribute( "allowMulti", data.mAllowMulti ? "true" : "false" );
if ( !data.mFilterAttributeColumn.isNull() )
editTypeElement.setAttribute( "filterAttributeColumn", data.mFilterAttributeColumn );
if ( !data.mFilterAttributeValue.isNull() )
editTypeElement.setAttribute( "filterAttributeValue", data.mFilterAttributeValue );
}
break;
case LineEdit:
case UniqueValues:
case UniqueValuesEditable:
case Classification:
case FileName:
case Hidden:
case TextEdit:
case Calendar:
case Enumeration:
case Immutable:
case UuidGenerator:
break;
}
editTypesElement.appendChild( editTypeElement );
}
node.appendChild( editTypesElement );
}
QDomElement efField = doc.createElement( "editform" );
QDomText efText = doc.createTextNode( QgsProject::instance()->writePath( mEditForm ) );
efField.appendChild( efText );
node.appendChild( efField );
QDomElement efiField = doc.createElement( "editforminit" );
QDomText efiText = doc.createTextNode( mEditFormInit );
efiField.appendChild( efiText );
node.appendChild( efiField );
QDomElement afField = doc.createElement( "annotationform" );
QDomText afText = doc.createTextNode( QgsProject::instance()->writePath( mAnnotationForm ) );
afField.appendChild( afText );
node.appendChild( afField );
//attribute aliases
if ( mAttributeAliasMap.size() > 0 )
{
QDomElement aliasElem = doc.createElement( "aliases" );
QMap<QString, QString>::const_iterator a_it = mAttributeAliasMap.constBegin();
for ( ; a_it != mAttributeAliasMap.constEnd(); ++a_it )
{
int idx = fieldNameIndex( a_it.key() );
if ( idx < 0 )
continue;
QDomElement aliasEntryElem = doc.createElement( "alias" );
aliasEntryElem.setAttribute( "field", a_it.key() );
aliasEntryElem.setAttribute( "index", idx );
aliasEntryElem.setAttribute( "name", a_it.value() );
aliasElem.appendChild( aliasEntryElem );
}
node.appendChild( aliasElem );
}
//exclude attributes WMS
QDomElement excludeWMSElem = doc.createElement( "excludeAttributesWMS" );
QSet<QString>::const_iterator attWMSIt = mExcludeAttributesWMS.constBegin();
for ( ; attWMSIt != mExcludeAttributesWMS.constEnd(); ++attWMSIt )
{
QDomElement attrElem = doc.createElement( "attribute" );
QDomText attrText = doc.createTextNode( *attWMSIt );
attrElem.appendChild( attrText );
excludeWMSElem.appendChild( attrElem );
}
node.appendChild( excludeWMSElem );
//exclude attributes WFS
QDomElement excludeWFSElem = doc.createElement( "excludeAttributesWFS" );
QSet<QString>::const_iterator attWFSIt = mExcludeAttributesWFS.constBegin();
for ( ; attWFSIt != mExcludeAttributesWFS.constEnd(); ++attWFSIt )
{
QDomElement attrElem = doc.createElement( "attribute" );
QDomText attrText = doc.createTextNode( *attWFSIt );
attrElem.appendChild( attrText );
excludeWFSElem.appendChild( attrElem );
}
node.appendChild( excludeWFSElem );
// tabs and groups of edit form
if ( mAttributeEditorElements.size() > 0 )
{
QDomElement tabsElem = doc.createElement( "attributeEditorForm" );
for ( QList< QgsAttributeEditorElement* >::const_iterator it = mAttributeEditorElements.begin(); it != mAttributeEditorElements.end(); it++ )
{
QDomElement attributeEditorWidgetElem = ( *it )->toDomElement( doc );
tabsElem.appendChild( attributeEditorWidgetElem );
}
node.appendChild( tabsElem );
}
// add attribute actions
mActions->writeXML( node, doc );
//save vector overlays (e.g. diagrams)
QList<QgsVectorOverlay*>::const_iterator overlay_it = mOverlays.constBegin();
for ( ; overlay_it != mOverlays.constEnd(); ++overlay_it )
{
if ( *overlay_it )
{
( *overlay_it )->writeXML( mapLayerNode, doc );
}
}
return true;
}
bool QgsVectorLayer::readSld( const QDomNode& node, QString& errorMessage )
{
// get the Name element
QDomElement nameElem = node.firstChildElement( "Name" );
if ( nameElem.isNull() )
{
errorMessage = "Warning: Name element not found within NamedLayer while it's required.";
}
if ( hasGeometryType() )
{
setUsingRendererV2( true );
QgsFeatureRendererV2* r = QgsFeatureRendererV2::loadSld( node, geometryType(), errorMessage );
if ( !r )
return false;
setRendererV2( r );
}
return true;
}
bool QgsVectorLayer::writeSld( QDomNode& node, QDomDocument& doc, QString& errorMessage ) const
{
Q_UNUSED( errorMessage );
// store the Name element
QDomElement nameNode = doc.createElement( "se:Name" );
nameNode.appendChild( doc.createTextNode( name() ) );
node.appendChild( nameNode );
if ( hasGeometryType() )
{
if ( mUsingRendererV2 )
{
node.appendChild( mRendererV2->writeSld( doc, *this ) );
}
else
{
node.appendChild( doc.createComment( "Old Renderer not supported yet" ) );
return false;
}
}
return true;
}
bool QgsVectorLayer::changeGeometry( QgsFeatureId fid, QgsGeometry* geom )
{
if ( !mEditBuffer || !mDataProvider )
{
return false;
}
return mEditBuffer->changeGeometry( fid, geom );
}
bool QgsVectorLayer::changeAttributeValue( QgsFeatureId fid, int field, QVariant value, bool emitSignal )
{
if ( !mEditBuffer || !mDataProvider )
return false;
// TODO[MD]: emitSignal
return mEditBuffer->changeAttributeValue( fid, field, value );
}
bool QgsVectorLayer::addAttribute( const QgsField &field )
{
if ( !mEditBuffer || !mDataProvider )
return false;
return mEditBuffer->addAttribute( field );
}
bool QgsVectorLayer::addAttribute( QString name, QString type )
{
const QList< QgsVectorDataProvider::NativeType > &types = mDataProvider->nativeTypes();
int i;
for ( i = 0; i < types.size() && types[i].mTypeName != type; i++ )
;
return i < types.size() && addAttribute( QgsField( name, types[i].mType, type ) );
}
void QgsVectorLayer::addAttributeAlias( int attIndex, QString aliasString )
{
if ( attIndex < 0 || attIndex >= pendingFields().count() )
return;
QString name = pendingFields()[ attIndex ].name();
mAttributeAliasMap.insert( name, aliasString );
emit layerModified(); // TODO[MD]: should have a different signal?
}
void QgsVectorLayer::addAttributeEditorWidget( QgsAttributeEditorElement* data )
{
mAttributeEditorElements.append( data );
}
QString QgsVectorLayer::attributeAlias( int attributeIndex ) const
{
if ( attributeIndex < 0 || attributeIndex >= pendingFields().count() )
return "";
QString name = pendingFields()[ attributeIndex ].name();
return mAttributeAliasMap.value( name, "" );
}
QString QgsVectorLayer::attributeDisplayName( int attributeIndex ) const
{
QString displayName = attributeAlias( attributeIndex );
if ( displayName.isEmpty() )
{
const QgsFields& fields = pendingFields();
if ( attributeIndex >= 0 && attributeIndex < fields.count() )
{
displayName = fields[attributeIndex].name();
}
}
return displayName;
}
bool QgsVectorLayer::deleteAttribute( int index )
{
if ( !mEditBuffer || !mDataProvider )
return false;
return mEditBuffer->deleteAttribute( index );
}
bool QgsVectorLayer::deleteFeature( QgsFeatureId fid )
{
if ( !mEditBuffer )
return false;
bool res = mEditBuffer->deleteFeature(fid);
if (res)
mSelectedFeatureIds.remove( fid ); // remove it from selection
return res;
}
const QgsFields &QgsVectorLayer::pendingFields() const
{
return mUpdatedFields;
}
QgsAttributeList QgsVectorLayer::pendingAllAttributesList()
{
QgsAttributeList lst;
for ( int i = 0; i < mUpdatedFields.count(); ++i )
lst.append( i );
return lst;
}
int QgsVectorLayer::pendingFeatureCount()
{
return mDataProvider->featureCount() +
(mEditBuffer ? mEditBuffer->mAddedFeatures.size() - mEditBuffer->mDeletedFeatureIds.size() : 0 );
}
bool QgsVectorLayer::commitChanges()
{
mCommitErrors.clear();
if ( !mDataProvider )
{
mCommitErrors << tr( "ERROR: no provider" );
return false;
}
if ( !mEditBuffer )
{
mCommitErrors << tr( "ERROR: layer not editable" );
return false;
}
bool success = mEditBuffer->commitChanges( mCommitErrors );
if ( success )
{
delete mEditBuffer;
mEditBuffer = 0;
undoStack()->clear();
emit editingStopped();
}
else
{
QgsMessageLog::logMessage( tr( "Commit errors:\n %1" ).arg( mCommitErrors.join( "\n " ) ) );
}
updateFields();
mDataProvider->updateExtents();
//clear the cache image so markers don't appear anymore on next draw
setCacheImage( 0 );
return success;
}
const QStringList &QgsVectorLayer::commitErrors()
{
return mCommitErrors;
}
bool QgsVectorLayer::rollBack()
{
if ( !mEditBuffer )
{
return false;
}
mEditBuffer->rollBack();
if ( isModified() )
{
// new undo stack roll back method
// old method of calling every undo could cause many canvas refreshes
undoStack()->setIndex( 0 );
}
updateFields();
delete mEditBuffer;
mEditBuffer = 0;
emit editingStopped();
// invalidate the cache so the layer updates properly to show its original
// after the rollback
setCacheImage( 0 );
return true;
}
void QgsVectorLayer::setSelectedFeatures( const QgsFeatureIds& ids )
{
// TODO: check whether features with these ID exist
mSelectedFeatureIds = ids;
// invalidate cache
setCacheImage( 0 );
emit selectionChanged();
}
int QgsVectorLayer::selectedFeatureCount()
{
return mSelectedFeatureIds.size();
}
const QgsFeatureIds& QgsVectorLayer::selectedFeaturesIds() const
{
return mSelectedFeatureIds;
}
QgsFeatureList QgsVectorLayer::selectedFeatures()
{
QgsFeatureList features;
foreach ( QgsFeatureId fid, mSelectedFeatureIds )
{
features.push_back( QgsFeature() );
featureAtId( fid, features.back(), geometryType() != QGis::NoGeometry, true );
}
return features;
}
bool QgsVectorLayer::addFeatures( QgsFeatureList features, bool makeSelected )
{
if ( !mEditBuffer || !mDataProvider )
return false;
bool res = mEditBuffer->addFeatures( features );
if ( makeSelected )
{
mSelectedFeatureIds.clear();
for ( QgsFeatureList::iterator iter = features.begin(); iter != features.end(); ++iter )
mSelectedFeatureIds.insert( iter->id() );
// invalidate cache
setCacheImage( 0 );
emit selectionChanged();
}
return res;
}
bool QgsVectorLayer::snapPoint( QgsPoint& point, double tolerance )
{
if ( !hasGeometryType() )
return false;
QMultiMap<double, QgsSnappingResult> snapResults;
int result = snapWithContext( point, tolerance, snapResults, QgsSnapper::SnapToVertex );
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::SnappingType snap_to )
{
if ( !hasGeometryType() )
return 1;
if ( snappingTolerance <= 0 || !mDataProvider )
{
return 1;
}
QList<QgsFeature> featureList;
QgsRectangle searchRect( startPoint.x() - snappingTolerance, startPoint.y() - snappingTolerance,
startPoint.x() + snappingTolerance, startPoint.y() + snappingTolerance );
double sqrSnappingTolerance = snappingTolerance * snappingTolerance;
int n = 0;
QgsFeature f;
if ( mCache->cachedGeometriesRect().contains( searchRect ) )
{
QgsGeometryMap& cachedGeometries = mCache->cachedGeometries();
for ( QgsGeometryMap::iterator it = cachedGeometries.begin(); it != cachedGeometries.end() ; ++it )
{
QgsGeometry* g = &( it.value() );
if ( g->boundingBox().intersects( searchRect ) )
{
snapToGeometry( startPoint, it.key(), g, sqrSnappingTolerance, snappingResults, snap_to );
++n;
}
}
}
else
{
// snapping outside cached area
select( QgsAttributeList(), searchRect, true, true );
while ( nextFeature( f ) )
{
snapToGeometry( startPoint, f.id(), f.geometry(), sqrSnappingTolerance, snappingResults, snap_to );
++n;
}
}
return n == 0 ? 2 : 0;
}
void QgsVectorLayer::snapToGeometry( const QgsPoint& startPoint,
QgsFeatureId featureId,
QgsGeometry* geom,
double sqrSnappingTolerance,
QMultiMap<double, QgsSnappingResult>& snappingResults,
QgsSnapper::SnappingType snap_to ) const
{
if ( !geom )
{
return;
}
int atVertex, beforeVertex, afterVertex;
double sqrDistVertexSnap, sqrDistSegmentSnap;
QgsPoint snappedPoint;
QgsSnappingResult snappingResultVertex;
QgsSnappingResult snappingResultSegment;
if ( snap_to == QgsSnapper::SnapToVertex || snap_to == QgsSnapper::SnapToVertexAndSegment )
{
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::SnapToSegment || snap_to == QgsSnapper::SnapToVertexAndSegment ) // snap to segment
{
if ( geometryType() != QGis::Point ) // cannot snap to segment for points/multipoints
{
sqrDistSegmentSnap = geom->closestSegmentWithContext( startPoint, snappedPoint, afterVertex, NULL, crs().geographicFlag() ? 1e-12 : 1e-8 );
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 )
{
QgsVectorLayerEditUtils utils(this);
return utils.insertSegmentVerticesForSnap( snapResults );
}
QgsVectorLayer::VertexMarkerType QgsVectorLayer::currentVertexMarkerType()
{
QSettings settings;
QString markerTypeString = settings.value( "/qgis/digitizing/marker_style", "Cross" ).toString();
if ( markerTypeString == "Cross" )
{
return QgsVectorLayer::Cross;
}
else if ( markerTypeString == "SemiTransparentCircle" )
{
return QgsVectorLayer::SemiTransparentCircle;
}
else
{
return QgsVectorLayer::NoMarker;
}
}
int QgsVectorLayer::currentVertexMarkerSize()
{
QSettings settings;
return settings.value( "/qgis/digitizing/marker_size", 3 ).toInt();
}
void QgsVectorLayer::drawFeature( QgsRenderContext &renderContext,
QgsFeature& fet,
QImage * marker )
{
QPainter *p = renderContext.painter();
// 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 ( ! fet.isValid() )
{
return;
}
QgsGeometry* geom = fet.geometry();
if ( !geom )
{
return;
}
unsigned char* feature = geom->asWkb();
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, &renderContext.mapToPixel(), renderContext.coordinateTransform() );
if ( qAbs( x ) > QgsClipper::MAX_X ||
qAbs( y ) > QgsClipper::MAX_Y )
{
break;
}
//QPointF pt(x - (marker->width()/2), y - (marker->height()/2));
QPointF pt( x * renderContext.rasterScaleFactor() - ( marker->width() / 2 ),
y * renderContext.rasterScaleFactor() - ( marker->height() / 2 ) );
p->save();
//p->scale(markerScaleFactor,markerScaleFactor);
p->scale( 1.0 / renderContext.rasterScaleFactor(), 1.0 / renderContext.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 / renderContext.rasterScaleFactor(), 1.0 / renderContext.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 );
transformPoint( x, y, &renderContext.mapToPixel(), renderContext.coordinateTransform() );
//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 * renderContext.rasterScaleFactor() - ( marker->width() / 2 ),
y * renderContext.rasterScaleFactor() - ( marker->height() / 2 ) );
//QPointF pt( x, y );
// Work around a +/- 32768 limitation on coordinates
if ( qAbs( x ) <= QgsClipper::MAX_X &&
qAbs( y ) <= QgsClipper::MAX_Y )
p->drawImage( pt, *marker );
}
p->restore();
break;
}
case QGis::WKBLineString:
case QGis::WKBLineString25D:
{
drawLineString( feature, renderContext );
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, renderContext );
}
break;
}
case QGis::WKBPolygon:
case QGis::WKBPolygon25D:
{
drawPolygon( feature, renderContext );
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, renderContext );
break;
}
default:
QgsDebugMsg( "Unknown WkbType ENCOUNTERED" );
break;
}
}
void QgsVectorLayer::setCoordinateSystem()
{
QgsDebugMsg( "----- Computing Coordinate System" );
//
// Get the layers project info and set up the QgsCoordinateTransform
// for this layer
//
if ( hasGeometryType() )
{
// get CRS directly from provider
setCrs( mDataProvider->crs() );
}
else
{
setCrs( QgsCoordinateReferenceSystem( GEO_EPSG_CRS_AUTHID ) );
}
}
// 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(
QVector<double>& x, QVector<double>& y, QVector<double>& z,
QgsRenderContext &renderContext )
{
// transform the point
if ( renderContext.coordinateTransform() )
renderContext.coordinateTransform()->transformInPlace( x, y, z );
// transform from projected coordinate system to pixel
// position on map canvas
renderContext.mapToPixel().transformInPlace( x, y );
}
const QString QgsVectorLayer::displayField() const
{
return mDisplayField;
}
bool QgsVectorLayer::isEditable() const
{
return ( mEditBuffer && mDataProvider );
}
bool QgsVectorLayer::isReadOnly() const
{
return mReadOnly;
}
bool QgsVectorLayer::setReadOnly( bool readonly )
{
// exit if the layer is in editing mode
if ( readonly && mEditBuffer )
return false;
mReadOnly = readonly;
return true;
}
bool QgsVectorLayer::isModified() const
{
return mEditBuffer && mEditBuffer->isModified();
}
QgsVectorLayer::EditType QgsVectorLayer::editType( int idx )
{
const QgsFields &fields = pendingFields();
if ( idx >= 0 && idx < fields.count() && mEditTypes.contains( fields[idx].name() ) )
return mEditTypes[ fields[idx].name()];
else
return LineEdit;
}
void QgsVectorLayer::setEditType( int idx, EditType type )
{
const QgsFields &fields = pendingFields();
if ( idx >= 0 && idx < fields.count() )
mEditTypes[ fields[idx].name()] = type;
}
QgsVectorLayer::EditorLayout QgsVectorLayer::editorLayout()
{
return mEditorLayout;
}
void QgsVectorLayer::setEditorLayout( EditorLayout editorLayout )
{
mEditorLayout = editorLayout;
}
QString QgsVectorLayer::editForm()
{
return mEditForm;
}
void QgsVectorLayer::setEditForm( QString ui )
{
mEditForm = ui;
}
void QgsVectorLayer::setAnnotationForm( const QString& ui )
{
mAnnotationForm = ui;
}
QString QgsVectorLayer::editFormInit()
{
return mEditFormInit;
}
void QgsVectorLayer::setEditFormInit( QString function )
{
mEditFormInit = function;
}
QMap< QString, QVariant > &QgsVectorLayer::valueMap( int idx )
{
const QgsFields &fields = pendingFields();
// FIXME: throw an exception!?
static QMap< QString, QVariant > invalidMap;
if ( idx < 0 || idx >= fields.count() )
{
QgsDebugMsg( QString( "field %1 not found" ).arg( idx ) );
return invalidMap;
}
QString fieldName = fields[idx].name();
if ( !mValueMaps.contains( fieldName ) )
mValueMaps[fieldName] = QMap<QString, QVariant>();
return mValueMaps[fieldName];
}
QgsVectorLayer::RangeData &QgsVectorLayer::range( int idx )
{
const QgsFields &fields = pendingFields();
// FIXME: throw an exception!?
static QgsVectorLayer::RangeData invalidRange;
if ( idx < 0 || idx >= fields.count() )
{
QgsDebugMsg( QString( "field %1 not found" ).arg( idx ) );
return invalidRange;
}
QString fieldName = fields[idx].name();
if ( !mRanges.contains( fieldName ) )
mRanges[fieldName] = RangeData();
return mRanges[fieldName];
}
void QgsVectorLayer::addOverlay( QgsVectorOverlay* overlay )
{
mOverlays.push_back( overlay );
}
void QgsVectorLayer::removeOverlay( const QString& typeName )
{
for ( int i = mOverlays.size() - 1; i >= 0; --i )
{
if ( mOverlays.at( i )->typeName() == typeName )
{
mOverlays.removeAt( i );
}
}
}
void QgsVectorLayer::vectorOverlays( QList<QgsVectorOverlay*>& overlayList )
{
overlayList = mOverlays;
}
QgsVectorOverlay* QgsVectorLayer::findOverlayByType( const QString& typeName )
{
QList<QgsVectorOverlay*>::iterator it = mOverlays.begin();
for ( ; it != mOverlays.end(); ++it )
{
if (( *it )->typeName() == typeName )
{
return *it;
}
}
return 0; //not found
}
QgsFeatureRendererV2* QgsVectorLayer::rendererV2()
{
return mRendererV2;
}
void QgsVectorLayer::setRendererV2( QgsFeatureRendererV2 *r )
{
if ( !hasGeometryType() )
return;
if ( r != mRendererV2 )
{
if ( r )
setUsingRendererV2( true );
delete mRendererV2;
mRendererV2 = r;
mSymbolFeatureCounted = false;
mSymbolFeatureCountMap.clear();
}
}
bool QgsVectorLayer::isUsingRendererV2()
{
return mUsingRendererV2;
}
void QgsVectorLayer::setUsingRendererV2( bool usingRendererV2 )
{
if ( !hasGeometryType() )
return;
mUsingRendererV2 = usingRendererV2;
}
void QgsVectorLayer::beginEditCommand( QString text )
{
undoStack()->beginMacro(text);
}
void QgsVectorLayer::endEditCommand()
{
undoStack()->endMacro();
}
void QgsVectorLayer::destroyEditCommand()
{
undoStack()->endMacro();
undoStack()->undo();
}
void QgsVectorLayer::setCheckedState( int idx, QString checked, QString unchecked )
{
const QgsFields &fields = pendingFields();
if ( idx >= 0 && idx < fields.count() )
mCheckedStates[ fields[idx].name()] = QPair<QString, QString>( checked, unchecked );
}
QPair<QString, QString> QgsVectorLayer::checkedState( int idx )
{
const QgsFields &fields = pendingFields();
if ( idx >= 0 && idx < fields.count() && mCheckedStates.contains( fields[idx].name() ) )
return mCheckedStates[ fields[idx].name()];
else
return QPair<QString, QString>( "1", "0" );
}
int QgsVectorLayer::fieldNameIndex( const QString& fieldName ) const
{
const QgsFields &theFields = pendingFields();
for ( int idx = 0; idx < theFields.count(); ++idx )
{
if ( QString::compare( theFields[idx].name(), fieldName, Qt::CaseInsensitive ) == 0 )
{
return idx;
}
}
return -1;
}
void QgsVectorLayer::addJoin( const QgsVectorJoinInfo& joinInfo )
{
mJoinBuffer->addJoin( joinInfo );
updateFields();
}
void QgsVectorLayer::checkJoinLayerRemove( QString theLayerId )
{
removeJoin( theLayerId );
}
void QgsVectorLayer::removeJoin( const QString& joinLayerId )
{
mJoinBuffer->removeJoin( joinLayerId );
updateFields();
}
const QList< QgsVectorJoinInfo >& QgsVectorLayer::vectorJoins() const
{
return mJoinBuffer->vectorJoins();
}
void QgsVectorLayer::updateFields()
{
if ( !mDataProvider )
return;
mUpdatedFields = mDataProvider->fields();
// added / removed fields
if ( mEditBuffer )
mEditBuffer->updateFields( mUpdatedFields );
// joined fields
if ( mJoinBuffer->containsJoins() )
mJoinBuffer->updateFields( mUpdatedFields );
}
void QgsVectorLayer::createJoinCaches()
{
if ( mJoinBuffer->containsJoins() )
{
mJoinBuffer->createJoinCaches();
}
}
void QgsVectorLayer::uniqueValues( int index, QList<QVariant> &uniqueValues, int limit )
{
uniqueValues.clear();
if ( !mDataProvider )
{
return;
}
QgsFields::FieldOrigin origin = mUpdatedFields.fieldOrigin(index);
if ( origin == QgsFields::OriginProvider ) //a provider field
{
return mDataProvider->uniqueValues( index, uniqueValues, limit );
}
else if ( origin == QgsFields::OriginJoin )
{
int sourceLayerIndex;
const QgsVectorJoinInfo* join = mJoinBuffer->joinForFieldIndex( index, mUpdatedFields, sourceLayerIndex );
Q_ASSERT( join );
QgsVectorLayer* vl = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( join->joinLayerId ) );
Q_ASSERT( vl );
return vl->dataProvider()->uniqueValues( sourceLayerIndex, uniqueValues, limit );
}
else if ( origin == QgsFields::OriginEdit )
{
// the layer is editable, but in certain cases it can still be avoided going through all features
if ( mEditBuffer->mDeletedFeatureIds.isEmpty() && mEditBuffer->mAddedFeatures.isEmpty() && !mEditBuffer->mDeletedAttributeIds.contains( index ) && mEditBuffer->mChangedAttributeValues.isEmpty() )
{
return mDataProvider->uniqueValues( index, uniqueValues, limit );
}
// we need to go through each feature
QgsAttributeList attList;
attList << index;
select( attList, QgsRectangle(), false, false );
QgsFeature f;
QVariant currentValue;
QHash<QString, QVariant> val;
while ( nextFeature( f ) )
{
currentValue = f.attribute( index );
val.insert( currentValue.toString(), currentValue );
if ( limit >= 0 && val.size() >= limit )
{
break;
}
}
uniqueValues = val.values();
return;
}
Q_ASSERT_X(false, "QgsVectorLayer::uniqueValues()", "Unknown source of the field!");
}
QVariant QgsVectorLayer::minimumValue( int index )
{
if ( !mDataProvider )
{
return QVariant();
}
QgsFields::FieldOrigin origin = mUpdatedFields.fieldOrigin(index);
if ( origin == QgsFields::OriginProvider ) //a provider field
{
return mDataProvider->minimumValue( index );
}
else if ( origin == QgsFields::OriginJoin )
{
int sourceLayerIndex;
const QgsVectorJoinInfo* join = mJoinBuffer->joinForFieldIndex( index, mUpdatedFields, sourceLayerIndex );
Q_ASSERT( join );
QgsVectorLayer* vl = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( join->joinLayerId ) );
Q_ASSERT( vl );
return vl->minimumValue( sourceLayerIndex );
}
else if ( origin == QgsFields::OriginEdit )
{
// the layer is editable, but in certain cases it can still be avoided going through all features
if ( mEditBuffer->mDeletedFeatureIds.isEmpty() && mEditBuffer->mAddedFeatures.isEmpty() && !mEditBuffer->mDeletedAttributeIds.contains( index ) && mEditBuffer->mChangedAttributeValues.isEmpty() )
{
return mDataProvider->minimumValue( index );
}
// we need to go through each feature
QgsAttributeList attList;
attList << index;
select( attList, QgsRectangle(), false, false );
QgsFeature f;
double minimumValue = std::numeric_limits<double>::max();
double currentValue = 0;
while ( nextFeature( f ) )
{
currentValue = f.attribute( index ).toDouble();
if ( currentValue < minimumValue )
{
minimumValue = currentValue;
}
}
return QVariant( minimumValue );
}
Q_ASSERT_X(false, "QgsVectorLayer::minimumValue()", "Unknown source of the field!");
return QVariant();
}
QVariant QgsVectorLayer::maximumValue( int index )
{
if ( !mDataProvider )
{
return QVariant();
}
QgsFields::FieldOrigin origin = mUpdatedFields.fieldOrigin(index);
if ( origin == QgsFields::OriginProvider ) //a provider field
{
return mDataProvider->maximumValue( index );
}
else if ( origin == QgsFields::OriginJoin )
{
int sourceLayerIndex;
const QgsVectorJoinInfo* join = mJoinBuffer->joinForFieldIndex( index, mUpdatedFields, sourceLayerIndex );
Q_ASSERT( join );
QgsVectorLayer* vl = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( join->joinLayerId ) );
Q_ASSERT( vl );
return vl->maximumValue( index );
}
else if ( origin == QgsFields::OriginEdit )
{
// the layer is editable, but in certain cases it can still be avoided going through all features
if ( mEditBuffer->mDeletedFeatureIds.isEmpty() && mEditBuffer->mAddedFeatures.isEmpty() && !mEditBuffer->mDeletedAttributeIds.contains( index ) && mEditBuffer->mChangedAttributeValues.isEmpty() )
{
return mDataProvider->maximumValue( index );
}
// we need to go through each feature
QgsAttributeList attList;
attList << index;
select( attList, QgsRectangle(), false, false );
QgsFeature f;
double maximumValue = -std::numeric_limits<double>::max();
double currentValue = 0;
while ( nextFeature( f ) )
{
currentValue = f.attribute( index ).toDouble();
if ( currentValue > maximumValue )
{
maximumValue = currentValue;
}
}
return QVariant( maximumValue );
}
Q_ASSERT_X(false, "QgsVectorLayer::maximumValue()", "Unknown source of the field!");
return QVariant();
}
void QgsVectorLayer::stopRendererV2( QgsRenderContext& rendererContext, QgsSingleSymbolRendererV2* selRenderer )
{
mRendererV2->stopRender( rendererContext );
if ( selRenderer )
{
selRenderer->stopRender( rendererContext );
delete selRenderer;
}
}
void QgsVectorLayer::prepareLabelingAndDiagrams( QgsRenderContext& rendererContext, QgsAttributeList& attributes, bool& labeling )
{
if ( !rendererContext.labelingEngine() )
return;
QSet<int> attrIndex;
if ( rendererContext.labelingEngine()->prepareLayer( this, attrIndex, rendererContext ) )
{
QSet<int>::const_iterator attIt = attrIndex.constBegin();
for ( ; attIt != attrIndex.constEnd(); ++attIt )
{
if ( !attributes.contains( *attIt ) )
{
attributes << *attIt;
}
}
labeling = true;
}
if ( labeling )
{
// see if feature count limit is set for labeling
QgsPalLayerSettings& palyr = rendererContext.labelingEngine()->layer( this->id() );
if ( palyr.limitNumLabels && palyr.maxNumLabels > 0 )
{
select( QgsAttributeList(), rendererContext.extent() );
// total number of features that may be labeled
QgsFeature ftr;
int nFeatsToLabel = 0;
while ( nextFeature( ftr ) ) { nFeatsToLabel += 1; }
palyr.mFeaturesToLabel = nFeatsToLabel;
}
}
//register diagram layers
if ( mDiagramRenderer && mDiagramLayerSettings )
{
mDiagramLayerSettings->renderer = mDiagramRenderer;
rendererContext.labelingEngine()->addDiagramLayer( this, mDiagramLayerSettings );
//add attributes needed by the diagram renderer
QList<int> att = mDiagramRenderer->diagramAttributes();
QList<int>::const_iterator attIt = att.constBegin();
for ( ; attIt != att.constEnd(); ++attIt )
{
if ( !attributes.contains( *attIt ) )
{
attributes << *attIt;
}
}
//and the ones needed for data defined diagram positions
if ( mDiagramLayerSettings->xPosColumn >= 0 && !attributes.contains( mDiagramLayerSettings->xPosColumn ) )
{
attributes << mDiagramLayerSettings->xPosColumn;
}
if ( mDiagramLayerSettings->yPosColumn >= 0 && !attributes.contains( mDiagramLayerSettings->yPosColumn ) )
{
attributes << mDiagramLayerSettings->yPosColumn;
}
}
}
void QgsVectorLayer::setDiagramLayerSettings( const QgsDiagramLayerSettings& s )
{
if ( !mDiagramLayerSettings )
mDiagramLayerSettings = new QgsDiagramLayerSettings();
*mDiagramLayerSettings = s;
}
QString QgsVectorLayer::metadata()
{
QString myMetadata = "<html><body>";
myMetadata += "<table width=\"100%\">";
//-------------
myMetadata += "<tr class=\"glossy\"><td>";
myMetadata += tr( "General:" );
myMetadata += "</td></tr>";
// data comment
if ( !( dataComment().isEmpty() ) )
{
myMetadata += "<tr><td>";
myMetadata += tr( "Layer comment: %1" ).arg( dataComment() );
myMetadata += "</td></tr>";
}
//storage type
myMetadata += "<tr><td>";
myMetadata += tr( "Storage type of this layer: %1" ).arg( storageType() );
myMetadata += "</td></tr>";
// data source
myMetadata += "<tr><td>";
myMetadata += tr( "Source for this layer: %1" ).arg( publicSource() );
myMetadata += "</td></tr>";
//geom type
QGis::GeometryType type = geometryType();
if ( type < 0 || type > QGis::NoGeometry )
{
QgsDebugMsg( "Invalid vector type" );
}
else
{
QString typeString( QGis::qgisVectorGeometryType[geometryType()] );
myMetadata += "<tr><td>";
myMetadata += tr( "Geometry type of the features in this layer: %1" ).arg( typeString );
myMetadata += "</td></tr>";
}
//feature count
myMetadata += "<tr><td>";
myMetadata += tr( "The number of features in this layer: %1" ).arg( featureCount() );
myMetadata += "</td></tr>";
//capabilities
myMetadata += "<tr><td>";
myMetadata += tr( "Editing capabilities of this layer: %1" ).arg( capabilitiesString() );
myMetadata += "</td></tr>";
//-------------
QgsRectangle myExtent = extent();
myMetadata += "<tr class=\"glossy\"><td>";
myMetadata += tr( "Extents:" );
myMetadata += "</td></tr>";
//extents in layer cs TODO...maybe make a little nested table to improve layout...
myMetadata += "<tr><td>" + tr( "In layer spatial reference system units : " );
// Try to be a bit clever over what number format we use for the
// extents. Some people don't like it using scientific notation when the
// numbers get large, but for small numbers this is the more practical
// option (so we can't force the format to 'f' for all values).
// The scheme:
// - for all numbers with more than 5 digits, force non-scientific notation
// and 2 digits after the decimal point.
// - for all smaller numbers let the OS decide which format to use (it will
// generally use non-scientific unless the number gets much less than 1).
if ( !myExtent.isEmpty() )
{
QString xMin, yMin, xMax, yMax;
double changeoverValue = 99999; // The 'largest' 5 digit number
if ( qAbs( myExtent.xMinimum() ) > changeoverValue )
{
xMin = QString( "%1" ).arg( myExtent.xMinimum(), 0, 'f', 2 );
}
else
{
xMin = QString( "%1" ).arg( myExtent.xMinimum() );
}
if ( qAbs( myExtent.yMinimum() ) > changeoverValue )
{
yMin = QString( "%1" ).arg( myExtent.yMinimum(), 0, 'f', 2 );
}
else
{
yMin = QString( "%1" ).arg( myExtent.yMinimum() );
}
if ( qAbs( myExtent.xMaximum() ) > changeoverValue )
{
xMax = QString( "%1" ).arg( myExtent.xMaximum(), 0, 'f', 2 );
}
else
{
xMax = QString( "%1" ).arg( myExtent.xMaximum() );
}
if ( qAbs( myExtent.yMaximum() ) > changeoverValue )
{
yMax = QString( "%1" ).arg( myExtent.yMaximum(), 0, 'f', 2 );
}
else
{
yMax = QString( "%1" ).arg( myExtent.yMaximum() );
}
myMetadata += tr( "xMin,yMin %1,%2 : xMax,yMax %3,%4" )
.arg( xMin ).arg( yMin ).arg( xMax ).arg( yMax );
}
else
{
myMetadata += tr( "unknown extent" );
}
myMetadata += "</td></tr>";
//extents in project cs
try
{
#if 0
// TODO: currently disabled, will revisit later [MD]
QgsRectangle myProjectedExtent = coordinateTransform->transformBoundingBox( extent() );
myMetadata += "<tr><td>";
myMetadata += tr( "In project spatial reference system units : " )
+ tr( "xMin,yMin %1,%2 : xMax,yMax %3,%4" )
.arg( myProjectedExtent.xMinimum() )
.arg( myProjectedExtent.yMinimum() )
.arg( myProjectedExtent.xMaximum() )
.arg( myProjectedExtent.yMaximum() );
myMetadata += "</td></tr>";
#endif
//
// Display layer spatial ref system
//
myMetadata += "<tr class=\"glossy\"><td>";
myMetadata += tr( "Layer Spatial Reference System:" );
myMetadata += "</td></tr>";
myMetadata += "<tr><td>";
myMetadata += crs().toProj4().replace( QRegExp( "\"" ), " \"" );
myMetadata += "</td></tr>";
//
// Display project (output) spatial ref system
//
#if 0
// TODO: disabled for now, will revisit later [MD]
myMetadata += "<tr><td bgcolor=\"gray\">";
myMetadata += tr( "Project (Output) Spatial Reference System:" );
myMetadata += "</td></tr>";
myMetadata += "<tr><td>";
myMetadata += coordinateTransform->destCRS().toProj4().replace( QRegExp( "\"" ), " \"" );
myMetadata += "</td></tr>";
#endif
}
catch ( QgsCsException &cse )
{
Q_UNUSED( cse );
QgsDebugMsg( cse.what() );
myMetadata += "<tr><td>";
myMetadata += tr( "In project spatial reference system units : " )
+ tr( "(Invalid transformation of layer extents)" );
myMetadata += "</td></tr>";
}
#if 0
//
// Add the info about each field in the attribute table
//
myMetadata += "<tr class=\"glossy\"><td>";
myMetadata += tr( "Attribute field info:" );
myMetadata += "</td></tr>";
myMetadata += "<tr><td>";
// Start a nested table in this trow
myMetadata += "<table width=\"100%\">";
myMetadata += "<tr><th>";
myMetadata += tr( "Field" );
myMetadata += "</th>";
myMetadata += "<th>";
myMetadata += tr( "Type" );
myMetadata += "</th>";
myMetadata += "<th>";
myMetadata += tr( "Length" );
myMetadata += "</th>";
myMetadata += "<th>";
myMetadata += tr( "Precision" );
myMetadata += "</th>";
myMetadata += "<th>";
myMetadata += tr( "Comment" );
myMetadata += "</th>";
//get info for each field by looping through them
const QgsFieldMap& myFields = pendingFields();
for ( QgsFieldMap::const_iterator it = myFields.begin(); it != myFields.end(); ++it )
{
const QgsField& myField = *it;
myMetadata += "<tr><td>";
myMetadata += myField.name();
myMetadata += "</td>";
myMetadata += "<td>";
myMetadata += myField.typeName();
myMetadata += "</td>";
myMetadata += "<td>";
myMetadata += QString( "%1" ).arg( myField.length() );
myMetadata += "</td>";
myMetadata += "<td>";
myMetadata += QString( "%1" ).arg( myField.precision() );
myMetadata += "</td>";
myMetadata += "<td>";
myMetadata += QString( "%1" ).arg( myField.comment() );
myMetadata += "</td></tr>";
}
//close field list
myMetadata += "</table>"; //end of nested table
#endif
myMetadata += "</td></tr>"; //end of stats container table row
//
// Close the table
//
myMetadata += "</table>";
myMetadata += "</body></html>";
return myMetadata;
}
QgsVectorLayer::ValueRelationData &QgsVectorLayer::valueRelation( int idx )
{
const QgsFields &fields = pendingFields();
// FIXME: throw an exception!?
static QgsVectorLayer::ValueRelationData invalidData;
if ( idx < 0 || idx >= fields.count() )
{
QgsDebugMsg( QString( "field %1 not found" ).arg( idx ) );
return invalidData;
}
QString fieldName = fields[idx].name();
if ( !mValueRelations.contains( fieldName ) )
{
mValueRelations[fieldName] = ValueRelationData();
}
return mValueRelations[fieldName];
}
QList<QgsAttributeEditorElement*> &QgsVectorLayer::attributeEditorElements()
{
return mAttributeEditorElements;
}
void QgsVectorLayer::clearAttributeEditorWidgets()
{
mAttributeEditorElements.clear();
}
QDomElement QgsAttributeEditorContainer::toDomElement( QDomDocument& doc ) const
{
QDomElement elem = doc.createElement( "attributeEditorContainer" );
elem.setAttribute( "name", mName );
for ( QList< QgsAttributeEditorElement* >::const_iterator it = mChildren.begin(); it != mChildren.end(); ++it )
{
elem.appendChild(( *it )->toDomElement( doc ) );
}
return elem;
}
void QgsAttributeEditorContainer::addChildElement( QgsAttributeEditorElement *widget )
{
mChildren.append( widget );
}
QDomElement QgsAttributeEditorField::toDomElement( QDomDocument& doc ) const
{
QDomElement elem = doc.createElement( "attributeEditorField" );
elem.setAttribute( "name", mName );
elem.setAttribute( "index", mIdx );
return elem;
}