Quantum GIS API Documentation  1.7.5-Wroclaw
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
qgssymbol.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  QgsSymbol.cpp - description
3  -------------------
4  begin : Sun Aug 11 2002
5  copyright : (C) 2002 by Gary E.Sherman
6  email : sherman at mrcc dot com
7  Romans 3:23=>Romans 6:23=>Romans 5:8=>Romans 10:9,10=>Romans 12
8  ***************************************************************************/
9 
10 /***************************************************************************
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * *
17  ***************************************************************************/
18 /* $Id$ */
19 #include <cmath>
20 
21 #include "qgis.h"
22 #include "qgssymbol.h"
23 #include "qgslogger.h"
24 #include "qgssymbologyutils.h"
25 #include "qgsmarkercatalogue.h"
26 #include "qgsapplication.h"
27 #include "qgsvectorlayer.h"
28 #include "qgsproject.h"
29 
30 #include <QPainter>
31 #include <QDomNode>
32 #include <QDomDocument>
33 #include <QImage>
34 #include <QDir>
35 #include <QFileInfo>
36 //#include <QString>
37 //do we have to include qstring?
38 
39 QgsSymbol::QgsSymbol( QGis::GeometryType t, QString lvalue, QString uvalue, QString label ) :
40  mLowerValue( lvalue ),
41  mUpperValue( uvalue ),
42  mLabel( label ),
43  mType( t ),
44  mPointSymbolName( "hard:circle" ),
45  mSize( DEFAULT_POINT_SIZE ),
46  mSizeInMapUnits( false ),
47  mPointSymbolImage( 1, 1, QImage::Format_ARGB32_Premultiplied ),
48  mWidthScale( -1.0 ),
49  mCacheUpToDate( false ),
50  mCacheUpToDate2( false ),
51  mRotationClassificationField( -1 ),
52  mScaleClassificationField( -1 ),
53  mSymbolField( -1 )
54 {
55  mPen.setWidthF( DEFAULT_LINE_WIDTH );
56 }
57 
58 
59 QgsSymbol::QgsSymbol( QGis::GeometryType t, QString lvalue, QString uvalue, QString label, QColor c ) :
60  mLowerValue( lvalue ),
61  mUpperValue( uvalue ),
62  mLabel( label ),
63  mType( t ),
64  mPen( c ),
65  mBrush( c ),
66  mPointSymbolName( "hard:circle" ),
67  mSize( DEFAULT_POINT_SIZE ),
68  mSizeInMapUnits( false ),
69  mPointSymbolImage( 1, 1, QImage::Format_ARGB32_Premultiplied ),
70  mWidthScale( -1.0 ),
71  mCacheUpToDate( false ),
72  mCacheUpToDate2( false ),
73  mRotationClassificationField( -1 ),
74  mScaleClassificationField( -1 ),
75  mSymbolField( -1 )
76 {
77  mPen.setWidthF( DEFAULT_LINE_WIDTH );
78 }
79 
81  : mPointSymbolName( "hard:circle" ),
82  mSize( DEFAULT_POINT_SIZE ),
83  mSizeInMapUnits( false ),
84  mPointSymbolImage( 1, 1, QImage::Format_ARGB32_Premultiplied ),
85  mWidthScale( -1.0 ),
86  mCacheUpToDate( false ),
87  mCacheUpToDate2( false ),
88  mRotationClassificationField( -1 ),
89  mScaleClassificationField( -1 ),
90  mSymbolField( -1 )
91 {
92  mPen.setWidthF( DEFAULT_LINE_WIDTH );
93 }
94 
95 
97  : mPen( c ),
98  mBrush( c ),
99  mPointSymbolName( "hard:circle" ),
100  mSize( DEFAULT_POINT_SIZE ),
101  mSizeInMapUnits( false ),
102  mPointSymbolImage( 1, 1, QImage::Format_ARGB32_Premultiplied ),
103  mWidthScale( -1.0 ),
104  mCacheUpToDate( false ),
105  mCacheUpToDate2( false ),
106  mRotationClassificationField( -1 ),
107  mScaleClassificationField( -1 ),
108  mSymbolField( -1 )
109 {
110  mPen.setWidthF( DEFAULT_LINE_WIDTH );
111 }
112 
114 {
115  if ( this != &s )
116  {
119  mLabel = s.mLabel;
120  mType = s.mType;
121  mPen = s.mPen;
122  mBrush = s.mBrush;
125  mSize = s.mSize;
139  }
140 }
141 
143 {
144 }
145 
146 
147 QColor QgsSymbol::color() const
148 {
149  return mPen.color();
150 }
151 
152 void QgsSymbol::setColor( QColor c )
153 {
154  mPen.setColor( c );
156 }
157 
158 QColor QgsSymbol::fillColor() const
159 {
160  return mBrush.color();
161 }
162 
163 void QgsSymbol::setFillColor( QColor c )
164 {
165  mBrush.setColor( c );
167 }
168 
169 double QgsSymbol::lineWidth() const
170 {
171  return mPen.widthF();
172 }
173 
174 void QgsSymbol::setLineWidth( double w )
175 {
176  mPen.setWidthF( w );
178 }
179 
180 void QgsSymbol::setLineStyle( Qt::PenStyle s )
181 {
182  mPen.setStyle( s );
184 }
185 
186 void QgsSymbol::setFillStyle( Qt::BrushStyle s )
187 {
188  mBrush.setStyle( s );
190 }
191 
193 {
194  return mTextureFilePath;
195 }
196 
197 void QgsSymbol::setCustomTexture( QString path )
198 {
199  mTextureFilePath = path;
200  mBrush.setTextureImage( QImage( path ) );
202 }
203 
204 //should we set the path independently of setting the texture?
205 
206 void QgsSymbol::setNamedPointSymbol( QString name )
207 {
208  if ( name.startsWith( "svg:" ) )
209  {
210  // do some sanity checking for svgs...
211  QString myTempName = name;
212  myTempName.replace( "svg:", "" );
213  QFile myFile( myTempName );
214  if ( !myFile.exists() )
215  {
216  QgsDebugMsg( "\n\n\n *** Svg Symbol not found on fs ***" );
217  QgsDebugMsg( "Name: " + name );
218  //see if we can resolve the problem...
219  //
220 
221  QStringList svgPaths = QgsApplication::svgPaths();
222  for ( int i = 0; i < svgPaths.size(); i++ )
223  {
224  QgsDebugMsg( "SvgPath: " + svgPaths[i] );
225  QFileInfo myInfo( myTempName );
226  QString myFileName = myInfo.fileName(); // foo.svg
227  QString myLowestDir = myInfo.dir().dirName();
228  QString myLocalPath = svgPaths[i] + "/" + myLowestDir + "/" + myFileName;
229 
230  QgsDebugMsg( "Alternative svg path: " + myLocalPath );
231  if ( QFile( myLocalPath ).exists() )
232  {
233  name = "svg:" + myLocalPath;
234  QgsDebugMsg( "Svg found in alternative path" );
235  }
236  else if ( myInfo.isRelative() )
237  {
238  QFileInfo pfi( QgsProject::instance()->fileName() );
239  if ( pfi.exists() && QFile( pfi.canonicalPath() + QDir::separator() + myTempName ).exists() )
240  {
241  name = "svg:" + pfi.canonicalPath() + QDir::separator() + myTempName;
242  QgsDebugMsg( "Svg found in alternative path" );
243  break;
244  }
245  else
246  {
247  QgsDebugMsg( "Svg not found in project path" );
248  }
249  }
250  else
251  {
252  //couldnt find the file, no happy ending :-(
253  QgsDebugMsg( "Computed alternate path but no svg there either" );
254  }
255  }
256  }
257  }
258  mPointSymbolName = name;
260 }
261 
263 {
264  return mPointSymbolName;
265 }
266 
267 void QgsSymbol::setPointSizeUnits( bool sizeInMapUnits )
268 {
269  mSizeInMapUnits = sizeInMapUnits;
270 }
271 
273 {
274  return mSizeInMapUnits;
275 }
276 
277 void QgsSymbol::setPointSize( double s )
278 {
279  if ( mSizeInMapUnits )
280  {
281  mSize = s;
282  }
283  else if ( s < MINIMUM_POINT_SIZE )
285  else
286  mSize = s;
287 
289 }
290 
291 double QgsSymbol::pointSize() const
292 {
293  return mSize;
294 }
295 
297 {
298  //Note by Tim: don't use premultiplied - it causes
299  //artifacts on the output icon!
300  QImage img( 15, 15, QImage::Format_ARGB32 );//QImage::Format_ARGB32_Premultiplied);
301  //0 = fully transparent
302  img.fill( QColor( 255, 255, 255, 0 ).rgba() );
303  QPainter p( &img );
304  p.setRenderHints( QPainter::Antialiasing );
305  p.setPen( mPen );
306 
307 
308  QPainterPath myPath;
309  myPath.moveTo( 0, 0 );
310  myPath.cubicTo( 15, 0, 5, 7, 15, 15 );
311  p.drawPath( myPath );
312  //p.drawLine(0, 0, 15, 15);
313  return img; //this is ok because of qts sharing mechanism
314 }
315 
317 {
318  //Note by Tim: don't use premultiplied - it causes
319  //artifacts on the output icon!
320  QImage img( 15, 15, QImage::Format_ARGB32 ); //, QImage::Format_ARGB32_Premultiplied);
321  //0 = fully transparent
322  img.fill( QColor( 255, 255, 255, 0 ).rgba() );
323  QPainter p( &img );
324  p.setRenderHints( QPainter::Antialiasing );
325  p.setPen( mPen );
326  p.setBrush( mBrush );
327  QPolygon myPolygon;
328  //leave a little white space around so
329  //don't draw at 0,0,15,15
330  myPolygon << QPoint( 2, 2 )
331  << QPoint( 1, 5 )
332  << QPoint( 1, 10 )
333  << QPoint( 2, 12 )
334  << QPoint( 5, 13 )
335  << QPoint( 6, 13 )
336  << QPoint( 8, 12 )
337  << QPoint( 8, 12 )
338  << QPoint( 10, 12 )
339  << QPoint( 12, 13 )
340  << QPoint( 13, 11 )
341  << QPoint( 12, 8 )
342  << QPoint( 11, 6 )
343  << QPoint( 12, 5 )
344  << QPoint( 13, 2 )
345  << QPoint( 11, 1 )
346  << QPoint( 10, 1 )
347  << QPoint( 8, 2 )
348  << QPoint( 6, 4 )
349  << QPoint( 4, 2 )
350  ;
351  p.drawPolygon( myPolygon );
352  //p.drawRect(1, 1, 14, 14);
353  return img; //this is ok because of qts sharing mechanism
354 }
355 
356 QImage QgsSymbol::getCachedPointSymbolAsImage( double widthScale, bool selected, QColor selectionColor, double opacity )
357 {
358  if ( !mCacheUpToDate2
359  || ( selected && mSelectionColor != selectionColor ) || ( opacity != mOpacity ) )
360  {
361  if ( selected )
362  {
363  cache2( widthScale, selectionColor, opacity );
364  }
365  else
366  {
367 
368  cache2( widthScale, mSelectionColor, opacity );
369  }
370  }
371 
372  if ( selected )
373  {
375  }
376  else
377  {
378  return mPointSymbolImage2;
379  }
380 }
381 
382 QImage QgsSymbol::getPointSymbolAsImage( double widthScale, bool selected, QColor selectionColor, double scale,
383  double rotation, double rasterScaleFactor, double opacity )
384 {
385  double scaleProduct = scale * rasterScaleFactor;
386 
387  //on systems where dpi in x- and y-direction are not the same, the scaleProduct may differ from 1.0 by a very small number
388  if ( scaleProduct > 0.9 && scaleProduct < 1.1 && 0 == rotation )
389  {
390  if ( mWidthScale < 0 || widthScale == mWidthScale )
391  {
392  // If scale is 1.0, rotation 0.0 use cached image.
393  return getCachedPointSymbolAsImage( widthScale, selected, selectionColor, opacity );
394  }
395  }
396 
397  QImage preRotateImage;
398  QPen pen = mPen;
399  double newWidth = mPen.widthF() * widthScale * rasterScaleFactor;
400  pen.setWidthF( newWidth );
401 
402  if ( selected )
403  {
404  pen.setColor( selectionColor );
405  QBrush brush = mBrush;
406  preRotateImage = QgsMarkerCatalogue::instance()->imageMarker(
408  ( float )( mSize * scale * widthScale * rasterScaleFactor ),
409  pen, mBrush, opacity );
410  }
411  else
412  {
413  QgsDebugMsgLevel( QString( "marker:%1 mPointSize:%2 mPointSizeUnits:%3 scale:%4 widthScale:%5 rasterScaleFactor:%6 opacity:%7" )
414  .arg( mPointSymbolName ).arg( mSize ).arg( mSizeInMapUnits ? "true" : "false" )
415  .arg( scale ).arg( widthScale ).arg( rasterScaleFactor ).arg( opacity ), 3 );
416 
417 
418  preRotateImage = QgsMarkerCatalogue::instance()->imageMarker(
420  ( float )( mSize * scale * widthScale * rasterScaleFactor ),
421  pen, mBrush, opacity );
422  }
423 
424  QMatrix rotationMatrix;
425  rotationMatrix = rotationMatrix.rotate( rotation );
426 
427  return preRotateImage.transformed( rotationMatrix, Qt::SmoothTransformation );
428 }
429 
430 
431 void QgsSymbol::cache( QColor selectionColor )
432 {
433  QPen pen = mPen;
434  pen.setColor( selectionColor );
435  QBrush brush = mBrush;
436  // For symbols that have a different colored border, the line
437  // below causes the fill color to be wrong for the print
438  // composer. Not sure why...
439  // brush.setColor ( selectionColor );
440 
442  mPen, mBrush );
443 
445  mPointSymbolName, mSize, pen, brush );
446 
447  mSelectionColor = selectionColor;
448  mCacheUpToDate = true;
449 }
450 
451 void QgsSymbol::cache2( double widthScale, QColor selectionColor, double opacity )
452 {
453 // QgsDebugMsg(QString("widthScale = %1").arg(widthScale));
454 
455  QPen pen = mPen;
456  pen.setWidthF( widthScale * pen.widthF() );
457 
459  pen, mBrush, opacity );
460 
461  QBrush brush = mBrush;
462  brush.setColor( selectionColor );
463  pen.setColor( selectionColor );
464 
466  mPointSymbolName, mSize * widthScale, pen, brush, opacity );
467 
468  mSelectionColor2 = selectionColor;
469 
470  mWidthScale = widthScale;
471 
472  mOpacity = opacity;
473 
474  mCacheUpToDate2 = true;
475 }
476 
477 void QgsSymbol::appendField( QDomElement &symbol, QDomDocument &document, const QgsVectorLayer &vl, QString name, int idx ) const
478 {
479  appendText( symbol, document, name, vl.pendingFields().contains( idx ) ? vl.pendingFields()[idx].name() : "" );
480 }
481 
482 void QgsSymbol::appendText( QDomElement &symbol, QDomDocument &document, QString name, QString value ) const
483 {
484  QDomElement node = document.createElement( name );
485  QDomText txt = document.createTextNode( value );
486  if ( value.isNull() )
487  {
488  node.setAttribute( "null", "1" );
489  }
490  symbol.appendChild( node );
491  node.appendChild( txt );
492 }
493 
494 bool QgsSymbol::writeXML( QDomNode & item, QDomDocument & document, const QgsVectorLayer *vl ) const
495 {
496  bool returnval = false;
497  returnval = true; // no error checking yet
498  QDomElement symbol = document.createElement( "symbol" );
499  item.appendChild( symbol );
500 
501  appendText( symbol, document, "lowervalue", mLowerValue );
502  appendText( symbol, document, "uppervalue", mUpperValue );
503  appendText( symbol, document, "label", mLabel );
504 
505  QString name = pointSymbolName();
506  if ( name.startsWith( "svg:" ) )
507  {
508  name = name.mid( 4 );
509 
510  QFileInfo fi( name );
511  if ( fi.exists() )
512  {
513  name = fi.canonicalFilePath();
514 
515  QStringList svgPaths = QgsApplication::svgPaths();
516 
517  for ( int i = 0; i < svgPaths.size(); i++ )
518  {
519  QString dir = QFileInfo( svgPaths[i] ).canonicalFilePath();
520 
521  if ( !dir.isEmpty() && name.startsWith( dir ) )
522  {
523  name = name.mid( dir.size() );
524  break;
525  }
526  }
527  }
528 
529  name = "svg:" + name;
530  }
531 
532  appendText( symbol, document, "pointsymbol", name );
533  appendText( symbol, document, "pointsize", QString::number( pointSize() ) );
534  appendText( symbol, document, "pointsizeunits", pointSizeUnits() ? "mapunits" : "pixels" );
535 
536  if ( vl )
537  {
538  appendField( symbol, document, *vl, "rotationclassificationfieldname", mRotationClassificationField );
539  appendField( symbol, document, *vl, "scaleclassificationfieldname", mScaleClassificationField );
540  appendField( symbol, document, *vl, "symbolfieldname", mSymbolField );
541  }
542 
543  QDomElement outlinecolor = document.createElement( "outlinecolor" );
544  outlinecolor.setAttribute( "red", QString::number( mPen.color().red() ) );
545  outlinecolor.setAttribute( "green", QString::number( mPen.color().green() ) );
546  outlinecolor.setAttribute( "blue", QString::number( mPen.color().blue() ) );
547  symbol.appendChild( outlinecolor );
548 
549  appendText( symbol, document, "outlinestyle", QgsSymbologyUtils::penStyle2QString( mPen.style() ) );
550  appendText( symbol, document, "outlinewidth", QString::number( mPen.widthF() ) );
551 
552  QDomElement fillcolor = document.createElement( "fillcolor" );
553  fillcolor.setAttribute( "red", QString::number( mBrush.color().red() ) );
554  fillcolor.setAttribute( "green", QString::number( mBrush.color().green() ) );
555  fillcolor.setAttribute( "blue", QString::number( mBrush.color().blue() ) );
556  symbol.appendChild( fillcolor );
557 
558  appendText( symbol, document, "fillpattern", QgsSymbologyUtils::brushStyle2QString( mBrush.style() ) );
559  appendText( symbol, document, "texturepath", QgsProject::instance()->writePath( mTextureFilePath ) );
560 
561  return returnval;
562 }
563 
564 int QgsSymbol::readFieldName( QDomNode &synode, QString name, const QgsVectorLayer &vl )
565 {
566  QDomNode node = synode.namedItem( name + "name" );
567 
568  if ( !node.isNull() )
569  {
570  const QgsFieldMap &fields = vl.pendingFields();
571  QString name = node.toElement().text();
572 
573  for ( QgsFieldMap::const_iterator it = fields.begin(); it != fields.end(); it++ )
574  if ( it->name() == name )
575  return it.key();
576 
577  return -1;
578  }
579 
580  node = synode.namedItem( name );
581 
582  return node.isNull() ? -1 : node.toElement().text().toInt();
583 }
584 
585 bool QgsSymbol::readXML( QDomNode &synode, const QgsVectorLayer *vl )
586 {
587  // Legacy project file formats didn't have support for pointsymbol nor
588  // pointsize Dom elements. Therefore we should check whether these
589  // actually exist.
590 
591  QDomNode lvalnode = synode.namedItem( "lowervalue" );
592  if ( ! lvalnode.isNull() )
593  {
594  QDomElement lvalelement = lvalnode.toElement();
595  if ( lvalelement.attribute( "null" ).toInt() == 1 )
596  {
597  mLowerValue = QString::null;
598  }
599  else
600  {
601  mLowerValue = lvalelement.text();
602  }
603  }
604 
605  QDomNode uvalnode = synode.namedItem( "uppervalue" );
606  if ( ! uvalnode.isNull() )
607  {
608  QDomElement uvalelement = uvalnode.toElement();
609  mUpperValue = uvalelement.text();
610  }
611 
612  QDomNode labelnode = synode.namedItem( "label" );
613  if ( ! labelnode.isNull() )
614  {
615  QDomElement labelelement = labelnode.toElement();
616  mLabel = labelelement.text();
617  }
618 
619  QDomNode psymbnode = synode.namedItem( "pointsymbol" );
620 
621  if ( ! psymbnode.isNull() )
622  {
623  QDomElement psymbelement = psymbnode.toElement();
624  setNamedPointSymbol( psymbelement.text() );
625  }
626 
627  QDomNode psizenode = synode.namedItem( "pointsize" );
628 
629  if ( ! psizenode.isNull() )
630  {
631  QDomElement psizeelement = psizenode.toElement();
632  setPointSize( psizeelement.text().toFloat() );
633  }
634 
635  QDomNode psizeunitnodes = synode.namedItem( "pointsizeunits" );
636  if ( ! psizeunitnodes.isNull() )
637  {
638  QDomElement psizeunitelement = psizeunitnodes.toElement();
639  QgsDebugMsg( QString( "psizeunitelement:%1" ).arg( psizeunitelement.text() ) );
640  setPointSizeUnits( psizeunitelement.text().compare( "mapunits", Qt::CaseInsensitive ) == 0 );
641  }
642 
643  if ( vl )
644  {
645  mRotationClassificationField = readFieldName( synode, "rotationclassificationfield", *vl );
646  mScaleClassificationField = readFieldName( synode, "scaleclassificationfield", *vl );
647  mSymbolField = readFieldName( synode, "symbolfield", *vl );
648  }
649  else
650  {
653  }
654 
655  QDomNode outlcnode = synode.namedItem( "outlinecolor" );
656  QDomElement oulcelement = outlcnode.toElement();
657  int red = oulcelement.attribute( "red" ).toInt();
658  int green = oulcelement.attribute( "green" ).toInt();
659  int blue = oulcelement.attribute( "blue" ).toInt();
660  setColor( QColor( red, green, blue ) );
661 
662  QDomNode outlstnode = synode.namedItem( "outlinestyle" );
663  QDomElement outlstelement = outlstnode.toElement();
664  setLineStyle( QgsSymbologyUtils::qString2PenStyle( outlstelement.text() ) );
665 
666  QDomNode outlwnode = synode.namedItem( "outlinewidth" );
667  QDomElement outlwelement = outlwnode.toElement();
668  setLineWidth( outlwelement.text().toDouble() );
669 
670  QDomNode fillcnode = synode.namedItem( "fillcolor" );
671  QDomElement fillcelement = fillcnode.toElement();
672  red = fillcelement.attribute( "red" ).toInt();
673  green = fillcelement.attribute( "green" ).toInt();
674  blue = fillcelement.attribute( "blue" ).toInt();
675  setFillColor( QColor( red, green, blue ) );
676 
677  QDomNode texturepathnode = synode.namedItem( "texturepath" );
678  QDomElement texturepathelement = texturepathnode.toElement();
679  setCustomTexture( QgsProject::instance()->readPath( texturepathelement.text() ) );
680 
681  //run this after setting the custom texture path, so we override the brush if it isn't the custom pattern brush.
682  QDomNode fillpnode = synode.namedItem( "fillpattern" );
683  QDomElement fillpelement = fillpnode.toElement();
684  setFillStyle( QgsSymbologyUtils::qString2BrushStyle( fillpelement.text() ) );
685 
686  return true;
687 }
688 
690 {
692 }
693 
695 {
697 }
698 
700 {
702 }
703 
705 {
707 }
708 
710 {
711  return mSymbolField;
712 }
713 
714 void QgsSymbol::setSymbolField( int field )
715 {
716  mSymbolField = field;
717 }