QGIS API Documentation  2.0.1-Dufour
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsmarkersymbollayerv2.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmarkersymbollayerv2.cpp
3  ---------------------
4  begin : November 2009
5  copyright : (C) 2009 by Martin Dobias
6  email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgsmarkersymbollayerv2.h"
17 #include "qgssymbollayerv2utils.h"
18 
19 #include "qgsexpression.h"
20 #include "qgsrendercontext.h"
21 #include "qgslogger.h"
22 #include "qgssvgcache.h"
23 
24 #include <QPainter>
25 #include <QSvgRenderer>
26 #include <QFileInfo>
27 #include <QDir>
28 #include <QDomDocument>
29 #include <QDomElement>
30 
31 #include <cmath>
32 
34 
35 QgsSimpleMarkerSymbolLayerV2::QgsSimpleMarkerSymbolLayerV2( QString name, QColor color, QColor borderColor, double size, double angle, QgsSymbolV2::ScaleMethod scaleMethod )
36  : mOutlineWidth( 0 ), mOutlineWidthUnit( QgsSymbolV2::MM )
37 {
38  mName = name;
39  mColor = color;
41  mSize = size;
42  mAngle = angle;
43  mOffset = QPointF( 0, 0 );
47 }
48 
50 {
57 
58  if ( props.contains( "name" ) )
59  name = props["name"];
60  if ( props.contains( "color" ) )
61  color = QgsSymbolLayerV2Utils::decodeColor( props["color"] );
62  if ( props.contains( "color_border" ) )
63  borderColor = QgsSymbolLayerV2Utils::decodeColor( props["color_border"] );
64  if ( props.contains( "size" ) )
65  size = props["size"].toDouble();
66  if ( props.contains( "angle" ) )
67  angle = props["angle"].toDouble();
68  if ( props.contains( "scale_method" ) )
69  scaleMethod = QgsSymbolLayerV2Utils::decodeScaleMethod( props["scale_method"] );
70 
71  QgsSimpleMarkerSymbolLayerV2* m = new QgsSimpleMarkerSymbolLayerV2( name, color, borderColor, size, angle, scaleMethod );
72  if ( props.contains( "offset" ) )
73  m->setOffset( QgsSymbolLayerV2Utils::decodePoint( props["offset"] ) );
74  if ( props.contains( "offset_unit" ) )
75  m->setOffsetUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["offset_unit"] ) );
76  if ( props.contains( "size_unit" ) )
77  m->setSizeUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["size_unit"] ) );
78 
79  if ( props.contains( "outline_width" ) )
80  {
81  m->setOutlineWidth( props["outline_width"].toDouble() );
82  }
83  if ( props.contains( "outline_width_unit" ) )
84  {
85  m->setOutlineWidthUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["outline_width_unit"] ) );
86  }
87 
88  //data defined properties
89  if ( props.contains( "name_expression" ) )
90  {
91  m->setDataDefinedProperty( "name", props["name_expression"] );
92  }
93  if ( props.contains( "color_expression" ) )
94  {
95  m->setDataDefinedProperty( "color", props["color_expression"] );
96  }
97  if ( props.contains( "color_border_expression" ) )
98  {
99  m->setDataDefinedProperty( "color_border", props["color_border_expression"] );
100  }
101  if ( props.contains( "outline_width_expression" ) )
102  {
103  m->setDataDefinedProperty( "outline_width", props["outline_width_expression"] );
104  }
105  if ( props.contains( "size_expression" ) )
106  {
107  m->setDataDefinedProperty( "size", props["size_expression"] );
108  }
109  if ( props.contains( "angle_expression" ) )
110  {
111  m->setDataDefinedProperty( "angle", props["angle_expression"] );
112  }
113  if ( props.contains( "offset_expression" ) )
114  {
115  m->setDataDefinedProperty( "offset", props["offset_expression"] );
116  }
117  return m;
118 }
119 
120 
122 {
123  return "SimpleMarker";
124 }
125 
127 {
128  QColor brushColor = mColor;
129  QColor penColor = mBorderColor;
130 
131  brushColor.setAlphaF( mColor.alphaF() * context.alpha() );
132  penColor.setAlphaF( mBorderColor.alphaF() * context.alpha() );
133 
134  mBrush = QBrush( brushColor );
135  mPen = QPen( penColor );
137 
138  QColor selBrushColor = context.renderContext().selectionColor();
139  QColor selPenColor = selBrushColor == mColor ? selBrushColor : mBorderColor;
140  if ( context.alpha() < 1 )
141  {
142  selBrushColor.setAlphaF( context.alpha() );
143  selPenColor.setAlphaF( context.alpha() );
144  }
145  mSelBrush = QBrush( selBrushColor );
146  mSelPen = QPen( selPenColor );
148 
149  bool hasDataDefinedRotation = context.renderHints() & QgsSymbolV2::DataDefinedRotation || dataDefinedProperty( "angle" );
150  bool hasDataDefinedSize = context.renderHints() & QgsSymbolV2::DataDefinedSizeScale || dataDefinedProperty( "size" );
151 
152  // use caching only when:
153  // - size, rotation, shape, color, border color is not data-defined
154  // - drawing to screen (not printer)
155  mUsingCache = !hasDataDefinedRotation && !hasDataDefinedSize && !context.renderContext().forceVectorOutput()
156  && !dataDefinedProperty( "name" ) && !dataDefinedProperty( "color" ) && !dataDefinedProperty( "color_border" ) && !dataDefinedProperty( "outline_width" ) &&
157  !dataDefinedProperty( "size" );
158 
159  // use either QPolygonF or QPainterPath for drawing
160  // TODO: find out whether drawing directly doesn't bring overhead - if not, use it for all shapes
161  if ( !prepareShape() ) // drawing as a polygon
162  {
163  if ( preparePath() ) // drawing as a painter path
164  {
165  // some markers can't be drawn as a polygon (circle, cross)
166  // For these set the selected border color to the selected color
167 
168  if ( mName != "circle" )
169  mSelPen.setColor( selBrushColor );
170  }
171  else
172  {
173  QgsDebugMsg( "unknown symbol" );
174  return;
175  }
176  }
177 
178  QMatrix transform;
179 
180  // scale the shape (if the size is not going to be modified)
181  if ( !hasDataDefinedSize )
182  {
183  double scaledSize = mSize * QgsSymbolLayerV2Utils::lineWidthScaleFactor( context.renderContext(), mSizeUnit );
184  if ( mUsingCache )
185  scaledSize *= context.renderContext().rasterScaleFactor();
186  double half = scaledSize / 2.0;
187  transform.scale( half, half );
188  }
189 
190  // rotate if the rotation is not going to be changed during the rendering
191  if ( !hasDataDefinedRotation && mAngle != 0 )
192  {
193  transform.rotate( mAngle );
194  }
195 
196  if ( !mPolygon.isEmpty() )
197  mPolygon = transform.map( mPolygon );
198  else
199  mPath = transform.map( mPath );
200 
201  if ( mUsingCache )
202  {
203  if ( !prepareCache( context ) )
204  {
205  mUsingCache = false;
206  }
207  }
208  else
209  {
210  mCache = QImage();
211  mSelCache = QImage();
212  }
213 
214  prepareExpressions( context.layer() );
215 }
216 
217 
219 {
220  double scaledSize = mSize * QgsSymbolLayerV2Utils::lineWidthScaleFactor( context.renderContext(), mSizeUnit );
221 
222  // calculate necessary image size for the cache
223  double pw = (( mPen.widthF() == 0 ? 1 : mPen.widthF() ) + 1 ) / 2 * 2; // make even (round up); handle cosmetic pen
224  int imageSize = (( int ) scaledSize + pw ) / 2 * 2 + 1; // make image width, height odd; account for pen width
225  double center = imageSize / 2.0;
226 
227  if ( imageSize > mMaximumCacheWidth )
228  {
229  return false;
230  }
231 
232  mCache = QImage( QSize( imageSize, imageSize ), QImage::Format_ARGB32_Premultiplied );
233  mCache.fill( 0 );
234 
235  QPainter p;
236  p.begin( &mCache );
237  p.setRenderHint( QPainter::Antialiasing );
238  p.setBrush( mBrush );
239  p.setPen( mPen );
240  p.translate( QPointF( center, center ) );
241  drawMarker( &p, context );
242  p.end();
243 
244  // Construct the selected version of the Cache
245 
246  QColor selColor = context.renderContext().selectionColor();
247 
248  mSelCache = QImage( QSize( imageSize, imageSize ), QImage::Format_ARGB32_Premultiplied );
249  mSelCache.fill( 0 );
250 
251  p.begin( &mSelCache );
252  p.setRenderHint( QPainter::Antialiasing );
253  p.setBrush( mSelBrush );
254  p.setPen( mSelPen );
255  p.translate( QPointF( center, center ) );
256  drawMarker( &p, context );
257  p.end();
258 
259  // Check that the selected version is different. If not, then re-render,
260  // filling the background with the selection color and using the normal
261  // colors for the symbol .. could be ugly!
262 
263  if ( mSelCache == mCache )
264  {
265  p.begin( &mSelCache );
266  p.setRenderHint( QPainter::Antialiasing );
267  p.fillRect( 0, 0, imageSize, imageSize, selColor );
268  p.setBrush( mBrush );
269  p.setPen( mPen );
270  p.translate( QPointF( center, center ) );
271  drawMarker( &p, context );
272  p.end();
273  }
274 
275  return true;
276 }
277 
279 {
280  Q_UNUSED( context );
281 }
282 
284 {
285  mPolygon.clear();
286 
287  if ( name.isNull() )
288  {
289  name = mName;
290  }
291 
292  if ( name == "square" || name == "rectangle" )
293  {
294  mPolygon = QPolygonF( QRectF( QPointF( -1, -1 ), QPointF( 1, 1 ) ) );
295  return true;
296  }
297  else if ( name == "diamond" )
298  {
299  mPolygon << QPointF( -1, 0 ) << QPointF( 0, 1 )
300  << QPointF( 1, 0 ) << QPointF( 0, -1 );
301  return true;
302  }
303  else if ( name == "pentagon" )
304  {
305  mPolygon << QPointF( sin( DEG2RAD( 288.0 ) ), - cos( DEG2RAD( 288.0 ) ) )
306  << QPointF( sin( DEG2RAD( 216.0 ) ), - cos( DEG2RAD( 216.0 ) ) )
307  << QPointF( sin( DEG2RAD( 144.0 ) ), - cos( DEG2RAD( 144.0 ) ) )
308  << QPointF( sin( DEG2RAD( 72.0 ) ), - cos( DEG2RAD( 72.0 ) ) )
309  << QPointF( 0, -1 );
310  return true;
311  }
312  else if ( name == "triangle" )
313  {
314  mPolygon << QPointF( -1, 1 ) << QPointF( 1, 1 ) << QPointF( 0, -1 );
315  return true;
316  }
317  else if ( name == "equilateral_triangle" )
318  {
319  mPolygon << QPointF( sin( DEG2RAD( 240.0 ) ), - cos( DEG2RAD( 240.0 ) ) )
320  << QPointF( sin( DEG2RAD( 120.0 ) ), - cos( DEG2RAD( 120.0 ) ) )
321  << QPointF( 0, -1 );
322  return true;
323  }
324  else if ( name == "star" )
325  {
326  double sixth = 1.0 / 3;
327 
328  mPolygon << QPointF( 0, -1 )
329  << QPointF( -sixth, -sixth )
330  << QPointF( -1, -sixth )
331  << QPointF( -sixth, 0 )
332  << QPointF( -1, 1 )
333  << QPointF( 0, + sixth )
334  << QPointF( 1, 1 )
335  << QPointF( + sixth, 0 )
336  << QPointF( 1, -sixth )
337  << QPointF( + sixth, -sixth );
338  return true;
339  }
340  else if ( name == "regular_star" )
341  {
342  double inner_r = cos( DEG2RAD( 72.0 ) ) / cos( DEG2RAD( 36.0 ) );
343 
344  mPolygon << QPointF( inner_r * sin( DEG2RAD( 324.0 ) ), - inner_r * cos( DEG2RAD( 324.0 ) ) ) // 324
345  << QPointF( sin( DEG2RAD( 288.0 ) ) , - cos( DEG2RAD( 288 ) ) ) // 288
346  << QPointF( inner_r * sin( DEG2RAD( 252.0 ) ), - inner_r * cos( DEG2RAD( 252.0 ) ) ) // 252
347  << QPointF( sin( DEG2RAD( 216.0 ) ) , - cos( DEG2RAD( 216.0 ) ) ) // 216
348  << QPointF( 0, inner_r ) // 180
349  << QPointF( sin( DEG2RAD( 144.0 ) ) , - cos( DEG2RAD( 144.0 ) ) ) // 144
350  << QPointF( inner_r * sin( DEG2RAD( 108.0 ) ), - inner_r * cos( DEG2RAD( 108.0 ) ) ) // 108
351  << QPointF( sin( DEG2RAD( 72.0 ) ) , - cos( DEG2RAD( 72.0 ) ) ) // 72
352  << QPointF( inner_r * sin( DEG2RAD( 36.0 ) ), - inner_r * cos( DEG2RAD( 36.0 ) ) ) // 36
353  << QPointF( 0, -1 ); // 0
354  return true;
355  }
356  else if ( name == "arrow" )
357  {
358  mPolygon
359  << QPointF( 0, -1 )
360  << QPointF( 0.5, -0.5 )
361  << QPointF( 0.25, -0.25 )
362  << QPointF( 0.25, 1 )
363  << QPointF( -0.25, 1 )
364  << QPointF( -0.25, -0.5 )
365  << QPointF( -0.5, -0.5 );
366  return true;
367  }
368  else if ( name == "filled_arrowhead" )
369  {
370  mPolygon << QPointF( 0, 0 ) << QPointF( -1, 1 ) << QPointF( -1, -1 );
371  return true;
372  }
373 
374  return false;
375 }
376 
378 {
379  mPath = QPainterPath();
380  if ( name.isNull() )
381  {
382  name = mName;
383  }
384 
385  if ( name == "circle" )
386  {
387  mPath.addEllipse( QRectF( -1, -1, 2, 2 ) ); // x,y,w,h
388  return true;
389  }
390  else if ( name == "cross" )
391  {
392  mPath.moveTo( -1, 0 );
393  mPath.lineTo( 1, 0 ); // horizontal
394  mPath.moveTo( 0, -1 );
395  mPath.lineTo( 0, 1 ); // vertical
396  return true;
397  }
398  else if ( name == "x" || name == "cross2" )
399  {
400  mPath.moveTo( -1, -1 );
401  mPath.lineTo( 1, 1 );
402  mPath.moveTo( 1, -1 );
403  mPath.lineTo( -1, 1 );
404  return true;
405  }
406  else if ( name == "line" )
407  {
408  mPath.moveTo( 0, -1 );
409  mPath.lineTo( 0, 1 ); // vertical line
410  return true;
411  }
412  else if ( name == "arrowhead" )
413  {
414  mPath.moveTo( 0, 0 );
415  mPath.lineTo( -1, -1 );
416  mPath.moveTo( 0, 0 );
417  mPath.lineTo( -1, 1 );
418  return true;
419  }
420 
421  return false;
422 }
423 
425 {
426  QPainter *p = context.renderContext().painter();
427  if ( !p )
428  {
429  return;
430  }
431 
432  //offset
433  double offsetX = 0;
434  double offsetY = 0;
435  markerOffset( context, offsetX, offsetY );
436  QPointF off( offsetX, offsetY );
437 
438  //angle
439  double angle = mAngle;
440  QgsExpression* angleExpression = expression( "angle" );
441  if ( angleExpression )
442  {
443  angle = angleExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
444  }
445  if ( angle )
446  off = _rotatedOffset( off, angle );
447 
448  //data defined shape?
449  QgsExpression* nameExpression = expression( "name" );
450  if ( nameExpression )
451  {
452  QString name = nameExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString();
453  if ( !prepareShape( name ) ) // drawing as a polygon
454  {
455  preparePath( name ); // drawing as a painter path
456  }
457  }
458 
459  if ( mUsingCache )
460  {
461  // we will use cached image
462  QImage &img = context.selected() ? mSelCache : mCache;
463  double s = img.width() / context.renderContext().rasterScaleFactor();
464  p->drawImage( QRectF( point.x() - s / 2.0 + off.x(),
465  point.y() - s / 2.0 + off.y(),
466  s, s ), img );
467  }
468  else
469  {
470  QMatrix transform;
471 
472  // move to the desired position
473  transform.translate( point.x() + off.x(), point.y() + off.y() );
474 
475  QgsExpression *sizeExpression = expression( "size" );
476  bool hasDataDefinedSize = context.renderHints() & QgsSymbolV2::DataDefinedSizeScale || sizeExpression;
477 
478  // resize if necessary
479  if ( hasDataDefinedSize )
480  {
481  double scaledSize = mSize;
482  if ( sizeExpression )
483  {
484  scaledSize = sizeExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
485  }
487 
488  switch ( mScaleMethod )
489  {
491  scaledSize = sqrt( scaledSize );
492  break;
494  break;
495  }
496 
497  double half = scaledSize / 2.0;
498  transform.scale( half, half );
499  }
500 
501  bool hasDataDefinedRotation = context.renderHints() & QgsSymbolV2::DataDefinedRotation || angleExpression;
502  if ( angle != 0 && hasDataDefinedRotation )
503  transform.rotate( angle );
504 
505  QgsExpression* colorExpression = expression( "color" );
506  QgsExpression* colorBorderExpression = expression( "color_border" );
507  QgsExpression* outlineWidthExpression = expression( "outline_width" );
508  if ( colorExpression )
509  {
510  mBrush.setColor( QgsSymbolLayerV2Utils::decodeColor( colorExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString() ) );
511  }
512  if ( colorBorderExpression )
513  {
514  mPen.setColor( QgsSymbolLayerV2Utils::decodeColor( colorBorderExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString() ) );
515  mSelPen.setColor( QgsSymbolLayerV2Utils::decodeColor( colorBorderExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString() ) );
516  }
517  if ( outlineWidthExpression )
518  {
519  double outlineWidth = outlineWidthExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
520  mPen.setWidthF( outlineWidth * QgsSymbolLayerV2Utils::lineWidthScaleFactor( context.renderContext(), mOutlineWidthUnit ) );
522  }
523 
524  p->setBrush( context.selected() ? mSelBrush : mBrush );
525  p->setPen( context.selected() ? mSelPen : mPen );
526 
527  if ( !mPolygon.isEmpty() )
528  p->drawPolygon( transform.map( mPolygon ) );
529  else
530  p->drawPath( transform.map( mPath ) );
531  }
532 }
533 
534 
536 {
537  QgsStringMap map;
538  map["name"] = mName;
539  map["color"] = QgsSymbolLayerV2Utils::encodeColor( mColor );
540  map["color_border"] = QgsSymbolLayerV2Utils::encodeColor( mBorderColor );
541  map["size"] = QString::number( mSize );
543  map["angle"] = QString::number( mAngle );
544  map["offset"] = QgsSymbolLayerV2Utils::encodePoint( mOffset );
547  map["outline_width"] = QString::number( mOutlineWidth );
548  map["outline_width_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mOutlineWidthUnit );
549 
550  //data define properties
552  return map;
553 }
554 
556 {
558  m->setOffset( mOffset );
559  m->setSizeUnit( mSizeUnit );
564  return m;
565 }
566 
567 void QgsSimpleMarkerSymbolLayerV2::writeSldMarker( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const
568 {
569  // <Graphic>
570  QDomElement graphicElem = doc.createElement( "se:Graphic" );
571  element.appendChild( graphicElem );
572 
574 
575  // <Rotation>
576  QString angleFunc;
577  bool ok;
578  double angle = props.value( "angle", "0" ).toDouble( &ok );
579  if ( !ok )
580  {
581  angleFunc = QString( "%1 + %2" ).arg( props.value( "angle", "0" ) ).arg( mAngle );
582  }
583  else if ( angle + mAngle != 0 )
584  {
585  angleFunc = QString::number( angle + mAngle );
586  }
587  QgsSymbolLayerV2Utils::createRotationElement( doc, graphicElem, angleFunc );
588 
589  // <Displacement>
591 }
592 
593 QString QgsSimpleMarkerSymbolLayerV2::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
594 {
595  Q_UNUSED( mmScaleFactor );
596  Q_UNUSED( mapUnitScaleFactor );
597 #if 0
598  QString ogrType = "3"; //default is circle
599  if ( mName == "square" )
600  {
601  ogrType = "5";
602  }
603  else if ( mName == "triangle" )
604  {
605  ogrType = "7";
606  }
607  else if ( mName == "star" )
608  {
609  ogrType = "9";
610  }
611  else if ( mName == "circle" )
612  {
613  ogrType = "3";
614  }
615  else if ( mName == "cross" )
616  {
617  ogrType = "0";
618  }
619  else if ( mName == "x" || mName == "cross2" )
620  {
621  ogrType = "1";
622  }
623  else if ( mName == "line" )
624  {
625  ogrType = "10";
626  }
627 
628  QString ogrString;
629  ogrString.append( "SYMBOL(" );
630  ogrString.append( "id:" );
631  ogrString.append( "\"" );
632  ogrString.append( "ogr-sym-" );
633  ogrString.append( ogrType );
634  ogrString.append( "\"" );
635  ogrString.append( ",c:" );
636  ogrString.append( mColor.name() );
637  ogrString.append( ",o:" );
638  ogrString.append( mBorderColor.name() );
639  ogrString.append( QString( ",s:%1mm" ).arg( mSize ) );
640  ogrString.append( ")" );
641  return ogrString;
642 #endif //0
643 
644  QString ogrString;
645  ogrString.append( "PEN(" );
646  ogrString.append( "c:" );
647  ogrString.append( mColor.name() );
648  ogrString.append( ",w:" );
649  ogrString.append( QString::number( mSize ) );
650  ogrString.append( "mm" );
651  ogrString.append( ")" );
652  return ogrString;
653 }
654 
656 {
657  QgsDebugMsg( "Entered." );
658 
659  QDomElement graphicElem = element.firstChildElement( "Graphic" );
660  if ( graphicElem.isNull() )
661  return NULL;
662 
663  QString name = "square";
664  QColor color, borderColor;
665  double borderWidth, size;
666 
667  if ( !QgsSymbolLayerV2Utils::wellKnownMarkerFromSld( graphicElem, name, color, borderColor, borderWidth, size ) )
668  return NULL;
669 
670  double angle = 0.0;
671  QString angleFunc;
672  if ( QgsSymbolLayerV2Utils::rotationFromSldElement( graphicElem, angleFunc ) )
673  {
674  bool ok;
675  double d = angleFunc.toDouble( &ok );
676  if ( ok )
677  angle = d;
678  }
679 
680  QPointF offset;
682 
683  QgsMarkerSymbolLayerV2 *m = new QgsSimpleMarkerSymbolLayerV2( name, color, borderColor, size );
684  m->setAngle( angle );
685  m->setOffset( offset );
686  return m;
687 }
688 
690 {
691  Q_UNUSED( context );
692 
693  if ( mPolygon.count() != 0 )
694  {
695  p->drawPolygon( mPolygon );
696  }
697  else
698  {
699  p->drawPath( mPath );
700  }
701 }
702 
704 
705 
707 {
709  mSize = size;
710  mAngle = angle;
711  mOffset = QPointF( 0, 0 );
712  mOutlineWidth = 1.0;
714  mFillColor = QColor( Qt::black );
715  mOutlineColor = QColor( Qt::black );
716 }
717 
718 
720 {
721  QString name = DEFAULT_SVGMARKER_NAME;
722  double size = DEFAULT_SVGMARKER_SIZE;
724 
725  if ( props.contains( "name" ) )
726  name = props["name"];
727  if ( props.contains( "size" ) )
728  size = props["size"].toDouble();
729  if ( props.contains( "angle" ) )
730  angle = props["angle"].toDouble();
731 
732  QgsSvgMarkerSymbolLayerV2* m = new QgsSvgMarkerSymbolLayerV2( name, size, angle );
733 
734  //we only check the svg default parameters if necessary, since it could be expensive
735  if ( !props.contains( "fill" ) && !props.contains( "outline" ) && !props.contains( "outline-width" ) )
736  {
737  QColor fillColor, outlineColor;
738  double outlineWidth;
739  bool hasFillParam, hasOutlineParam, hasOutlineWidthParam;
740  QgsSvgCache::instance()->containsParams( name, hasFillParam, fillColor, hasOutlineParam, outlineColor, hasOutlineWidthParam, outlineWidth );
741  if ( hasFillParam )
742  {
743  m->setFillColor( fillColor );
744  }
745  if ( hasOutlineParam )
746  {
747  m->setOutlineColor( outlineColor );
748  }
749  if ( hasOutlineWidthParam )
750  {
751  m->setOutlineWidth( outlineWidth );
752  }
753  }
754 
755  if ( props.contains( "size_unit" ) )
756  m->setSizeUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["size_unit"] ) );
757  if ( props.contains( "offset" ) )
758  m->setOffset( QgsSymbolLayerV2Utils::decodePoint( props["offset"] ) );
759  if ( props.contains( "offset_unit" ) )
760  m->setOffsetUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["offset_unit"] ) );
761  if ( props.contains( "fill" ) )
762  m->setFillColor( QColor( props["fill"] ) );
763  if ( props.contains( "outline" ) )
764  m->setOutlineColor( QColor( props["outline"] ) );
765  if ( props.contains( "outline-width" ) )
766  m->setOutlineWidth( props["outline-width"].toDouble() );
767  if ( props.contains( "outline_width_unit" ) )
768  m->setOutlineWidthUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["outline_width_unit"] ) );
769 
770  //data defined properties
771  if ( props.contains( "size_expression" ) )
772  {
773  m->setDataDefinedProperty( "size", props["size_expression"] );
774  }
775  if ( props.contains( "outline-width_expression" ) )
776  {
777  m->setDataDefinedProperty( "outline-width", props["outline-width_expression"] );
778  }
779  if ( props.contains( "angle_expression" ) )
780  {
781  m->setDataDefinedProperty( "angle", props["angle_expression"] );
782  }
783  if ( props.contains( "offset_expression" ) )
784  {
785  m->setDataDefinedProperty( "offset", props["offset_expression"] );
786  }
787  if ( props.contains( "name_expression" ) )
788  {
789  m->setDataDefinedProperty( "name", props["name_expression"] );
790  }
791  if ( props.contains( "fill_expression" ) )
792  {
793  m->setDataDefinedProperty( "fill", props["fill_expression"] );
794  }
795  if ( props.contains( "outline_expression" ) )
796  {
797  m->setDataDefinedProperty( "outline", props["outline_expression"] );
798  }
799  return m;
800 }
801 
803 {
804  mPath = path;
805  QColor fillColor, outlineColor;
806  double outlineWidth;
807  bool hasFillParam, hasOutlineParam, hasOutlineWidthParam;
808  QgsSvgCache::instance()->containsParams( path, hasFillParam, fillColor, hasOutlineParam, outlineColor, hasOutlineWidthParam, outlineWidth );
809  if ( hasFillParam )
810  {
811  setFillColor( fillColor );
812  }
813  if ( hasOutlineParam )
814  {
815  setOutlineColor( outlineColor );
816  }
817  if ( hasOutlineWidthParam )
818  {
819  setOutlineWidth( outlineWidth );
820  }
821 }
822 
823 
825 {
826  return "SvgMarker";
827 }
828 
830 {
831  mOrigSize = mSize; // save in case the size would be data defined
832  Q_UNUSED( context );
833  prepareExpressions( context.layer() );
834 }
835 
837 {
838  Q_UNUSED( context );
839 }
840 
842 {
843  QPainter *p = context.renderContext().painter();
844  if ( !p )
845  return;
846 
847  double size = mSize;
848  QgsExpression* sizeExpression = expression( "size" );
849  bool hasDataDefinedSize = context.renderHints() & QgsSymbolV2::DataDefinedSizeScale || sizeExpression;
850 
851  if ( sizeExpression )
852  {
853  size = sizeExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
854  }
856 
857  if ( hasDataDefinedSize )
858  {
859  switch ( mScaleMethod )
860  {
862  size = sqrt( size );
863  break;
865  break;
866  }
867  }
868 
869  //don't render symbols with size below one or above 10,000 pixels
870  if (( int )size < 1 || 10000.0 < size )
871  {
872  return;
873  }
874 
875  p->save();
876 
877  QPointF offset = mOffset;
878  QgsExpression* offsetExpression = expression( "offset" );
879  if ( offsetExpression )
880  {
881  QString offsetString = offsetExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString();
882  offset = QgsSymbolLayerV2Utils::decodePoint( offsetString );
883  }
884  double offsetX = offset.x() * QgsSymbolLayerV2Utils::lineWidthScaleFactor( context.renderContext(), mOffsetUnit );
885  double offsetY = offset.y() * QgsSymbolLayerV2Utils::lineWidthScaleFactor( context.renderContext(), mOffsetUnit );
886  QPointF outputOffset( offsetX, offsetY );
887 
888  double angle = mAngle;
889  QgsExpression* angleExpression = expression( "angle" );
890  if ( angleExpression )
891  {
892  angle = angleExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
893  }
894  if ( angle )
895  outputOffset = _rotatedOffset( outputOffset, angle );
896  p->translate( point + outputOffset );
897 
898  bool rotated = !qgsDoubleNear( angle, 0 );
899  bool drawOnScreen = qgsDoubleNear( context.renderContext().rasterScaleFactor(), 1.0, 0.1 );
900  if ( rotated )
901  p->rotate( angle );
902 
903  QString path = mPath;
904  QgsExpression* nameExpression = expression( "name" );
905  if ( nameExpression )
906  {
907  path = nameExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString();
908  }
909 
910  double outlineWidth = mOutlineWidth;
911  QgsExpression* outlineWidthExpression = expression( "outline_width" );
912  if ( outlineWidthExpression )
913  {
914  outlineWidth = outlineWidthExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
915  }
916 
917  QColor fillColor = mFillColor;
918  QgsExpression* fillExpression = expression( "fill" );
919  if ( fillExpression )
920  {
921  fillColor = QgsSymbolLayerV2Utils::decodeColor( fillExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString() );
922  }
923 
924  QColor outlineColor = mOutlineColor;
925  QgsExpression* outlineExpression = expression( "outline" );
926  if ( outlineExpression )
927  {
928  outlineColor = QgsSymbolLayerV2Utils::decodeColor( outlineExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString() );
929  }
930 
931 
932  bool fitsInCache = true;
933  bool usePict = true;
934  double hwRatio = 1.0;
935  if ( drawOnScreen && !rotated )
936  {
937  usePict = false;
938  const QImage& img = QgsSvgCache::instance()->svgAsImage( path, size, fillColor, outlineColor, outlineWidth,
939  context.renderContext().scaleFactor(), context.renderContext().rasterScaleFactor(), fitsInCache );
940  if ( fitsInCache && img.width() > 1 )
941  {
942  //consider transparency
943  if ( !qgsDoubleNear( context.alpha(), 1.0 ) )
944  {
945  QImage transparentImage = img.copy();
946  QgsSymbolLayerV2Utils::multiplyImageOpacity( &transparentImage, context.alpha() );
947  p->drawImage( -transparentImage.width() / 2.0, -transparentImage.height() / 2.0, transparentImage );
948  hwRatio = ( double )transparentImage.height() / ( double )transparentImage.width();
949  }
950  else
951  {
952  p->drawImage( -img.width() / 2.0, -img.height() / 2.0, img );
953  hwRatio = ( double )img.height() / ( double )img.width();
954  }
955  }
956  }
957 
958  if ( usePict || !fitsInCache )
959  {
960  p->setOpacity( context.alpha() );
961  const QPicture& pct = QgsSvgCache::instance()->svgAsPicture( path, size, fillColor, outlineColor, outlineWidth,
963 
964  if ( pct.width() > 1 )
965  {
966  p->drawPicture( 0, 0, pct );
967  hwRatio = ( double )pct.height() / ( double )pct.width();
968  }
969  }
970 
971  if ( context.selected() )
972  {
973  QPen pen( context.renderContext().selectionColor() );
975  if ( penWidth > size / 20 )
976  {
977  // keep the pen width from covering symbol
978  penWidth = size / 20;
979  }
980  double penOffset = penWidth / 2;
981  pen.setWidth( penWidth );
982  p->setPen( pen );
983  p->setBrush( Qt::NoBrush );
984  double wSize = size + penOffset;
985  double hSize = size * hwRatio + penOffset;
986  p->drawRect( QRectF( -wSize / 2.0, -hSize / 2.0, wSize, hSize ) );
987  }
988 
989  p->restore();
990 }
991 
992 
994 {
995  QgsStringMap map;
997  map["size"] = QString::number( mSize );
999  map["angle"] = QString::number( mAngle );
1000  map["offset"] = QgsSymbolLayerV2Utils::encodePoint( mOffset );
1001  map["offset_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mOffsetUnit );
1002  map["fill"] = mFillColor.name();
1003  map["outline"] = mOutlineColor.name();
1004  map["outline-width"] = QString::number( mOutlineWidth );
1005  map["outline_width_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mOutlineWidthUnit );
1007  return map;
1008 }
1009 
1011 {
1013  m->setFillColor( mFillColor );
1017  m->setOffset( mOffset );
1018  m->setOffsetUnit( mOffsetUnit );
1019  m->setSizeUnit( mSizeUnit );
1021  return m;
1022 }
1023 
1025 {
1026  mSizeUnit = unit;
1027  mOffsetUnit = unit;
1028  mOutlineWidthUnit = unit;
1029 }
1030 
1032 {
1034  if ( unit != mOffsetUnit || unit != mOutlineWidthUnit )
1035  {
1036  return QgsSymbolV2::Mixed;
1037  }
1038  return unit;
1039 }
1040 
1041 void QgsSvgMarkerSymbolLayerV2::writeSldMarker( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const
1042 {
1043  // <Graphic>
1044  QDomElement graphicElem = doc.createElement( "se:Graphic" );
1045  element.appendChild( graphicElem );
1046 
1047  QgsSymbolLayerV2Utils::externalGraphicToSld( doc, graphicElem, mPath, "image/svg+xml", mFillColor, mSize );
1048 
1049  // <Rotation>
1050  QString angleFunc;
1051  bool ok;
1052  double angle = props.value( "angle", "0" ).toDouble( &ok );
1053  if ( !ok )
1054  {
1055  angleFunc = QString( "%1 + %2" ).arg( props.value( "angle", "0" ) ).arg( mAngle );
1056  }
1057  else if ( angle + mAngle != 0 )
1058  {
1059  angleFunc = QString::number( angle + mAngle );
1060  }
1061 
1062  QgsSymbolLayerV2Utils::createRotationElement( doc, graphicElem, angleFunc );
1063 
1064  // <Displacement>
1066 }
1067 
1069 {
1070  QgsDebugMsg( "Entered." );
1071 
1072  QDomElement graphicElem = element.firstChildElement( "Graphic" );
1073  if ( graphicElem.isNull() )
1074  return NULL;
1075 
1076  QString path, mimeType;
1077  QColor fillColor;
1078  double size;
1079 
1080  if ( !QgsSymbolLayerV2Utils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
1081  return NULL;
1082 
1083  if ( mimeType != "image/svg+xml" )
1084  return NULL;
1085 
1086  double angle = 0.0;
1087  QString angleFunc;
1088  if ( QgsSymbolLayerV2Utils::rotationFromSldElement( graphicElem, angleFunc ) )
1089  {
1090  bool ok;
1091  double d = angleFunc.toDouble( &ok );
1092  if ( ok )
1093  angle = d;
1094  }
1095 
1096  QPointF offset;
1098 
1100  m->setFillColor( fillColor );
1101  //m->setOutlineColor( outlineColor );
1102  //m->setOutlineWidth( outlineWidth );
1103  m->setAngle( angle );
1104  m->setOffset( offset );
1105  return m;
1106 }
1107 
1109 
1110 QgsFontMarkerSymbolLayerV2::QgsFontMarkerSymbolLayerV2( QString fontFamily, QChar chr, double pointSize, QColor color, double angle )
1111 {
1113  mChr = chr;
1114  mColor = color;
1115  mAngle = angle;
1116  mSize = pointSize;
1118  mOffset = QPointF( 0, 0 );
1120 }
1121 
1123 {
1125  QChar chr = DEFAULT_FONTMARKER_CHR;
1126  double pointSize = DEFAULT_FONTMARKER_SIZE;
1129 
1130  if ( props.contains( "font" ) )
1131  fontFamily = props["font"];
1132  if ( props.contains( "chr" ) && props["chr"].length() > 0 )
1133  chr = props["chr"].at( 0 );
1134  if ( props.contains( "size" ) )
1135  pointSize = props["size"].toDouble();
1136  if ( props.contains( "color" ) )
1137  color = QgsSymbolLayerV2Utils::decodeColor( props["color"] );
1138  if ( props.contains( "angle" ) )
1139  angle = props["angle"].toDouble();
1140 
1141  QgsFontMarkerSymbolLayerV2* m = new QgsFontMarkerSymbolLayerV2( fontFamily, chr, pointSize, color, angle );
1142  if ( props.contains( "offset" ) )
1143  m->setOffset( QgsSymbolLayerV2Utils::decodePoint( props["offset"] ) );
1144  if ( props.contains( "offset_unit" ) )
1145  m->setOffsetUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["offset_unit" ] ) );
1146  if ( props.contains( "size_unit" ) )
1147  m->setSizeUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["size_unit"] ) );
1148  return m;
1149 }
1150 
1152 {
1153  return "FontMarker";
1154 }
1155 
1157 {
1158  mFont = QFont( mFontFamily );
1160  QFontMetrics fm( mFont );
1161  mChrOffset = QPointF( fm.width( mChr ) / 2, -fm.ascent() / 2 );
1162 
1163  mOrigSize = mSize; // save in case the size would be data defined
1164 }
1165 
1167 {
1168  Q_UNUSED( context );
1169 }
1170 
1172 {
1173  QPainter *p = context.renderContext().painter();
1174  if ( !p )
1175  return;
1176 
1177  QColor penColor = context.selected() ? context.renderContext().selectionColor() : mColor;
1178  penColor.setAlphaF( mColor.alphaF() * context.alpha() );
1179  p->setPen( penColor );
1180  p->setFont( mFont );
1181 
1182  p->save();
1185  QPointF outputOffset( offsetX, offsetY );
1186  if ( mAngle )
1187  outputOffset = _rotatedOffset( outputOffset, mAngle );
1188  p->translate( point + outputOffset );
1189 
1191  {
1192  double s = mSize / mOrigSize;
1193  p->scale( s, s );
1194  }
1195 
1196  if ( mAngle != 0 )
1197  p->rotate( mAngle );
1198 
1199  p->drawText( -mChrOffset, mChr );
1200  p->restore();
1201 }
1202 
1204 {
1205  QgsStringMap props;
1206  props["font"] = mFontFamily;
1207  props["chr"] = mChr;
1208  props["size"] = QString::number( mSize );
1209  props["size_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mSizeUnit );
1210  props["color"] = QgsSymbolLayerV2Utils::encodeColor( mColor );
1211  props["angle"] = QString::number( mAngle );
1212  props["offset"] = QgsSymbolLayerV2Utils::encodePoint( mOffset );
1213  props["offset_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mOffsetUnit );
1214  return props;
1215 }
1216 
1218 {
1220  m->setOffset( mOffset );
1221  m->setOffsetUnit( mOffsetUnit );
1222  m->setSizeUnit( mSizeUnit );
1223  return m;
1224 }
1225 
1226 void QgsFontMarkerSymbolLayerV2::writeSldMarker( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const
1227 {
1228  // <Graphic>
1229  QDomElement graphicElem = doc.createElement( "se:Graphic" );
1230  element.appendChild( graphicElem );
1231 
1232  QString fontPath = QString( "ttf://%1" ).arg( mFontFamily );
1233  int markIndex = mChr.unicode();
1234  QgsSymbolLayerV2Utils::externalMarkerToSld( doc, graphicElem, fontPath, "ttf", &markIndex, mColor, mSize );
1235 
1236  // <Rotation>
1237  QString angleFunc;
1238  bool ok;
1239  double angle = props.value( "angle", "0" ).toDouble( &ok );
1240  if ( !ok )
1241  {
1242  angleFunc = QString( "%1 + %2" ).arg( props.value( "angle", "0" ) ).arg( mAngle );
1243  }
1244  else if ( angle + mAngle != 0 )
1245  {
1246  angleFunc = QString::number( angle + mAngle );
1247  }
1248  QgsSymbolLayerV2Utils::createRotationElement( doc, graphicElem, angleFunc );
1249 
1250  // <Displacement>
1252 }
1253 
1255 {
1256  QgsDebugMsg( "Entered." );
1257 
1258  QDomElement graphicElem = element.firstChildElement( "Graphic" );
1259  if ( graphicElem.isNull() )
1260  return NULL;
1261 
1262  QString name, format;
1263  QColor color;
1264  double size;
1265  int chr;
1266 
1267  if ( !QgsSymbolLayerV2Utils::externalMarkerFromSld( graphicElem, name, format, chr, color, size ) )
1268  return NULL;
1269 
1270  if ( !name.startsWith( "ttf://" ) || format != "ttf" )
1271  return NULL;
1272 
1273  QString fontFamily = name.mid( 6 );
1274 
1275  double angle = 0.0;
1276  QString angleFunc;
1277  if ( QgsSymbolLayerV2Utils::rotationFromSldElement( graphicElem, angleFunc ) )
1278  {
1279  bool ok;
1280  double d = angleFunc.toDouble( &ok );
1281  if ( ok )
1282  angle = d;
1283  }
1284 
1285  QPointF offset;
1287 
1288  QgsMarkerSymbolLayerV2 *m = new QgsFontMarkerSymbolLayerV2( fontFamily, chr, size, color );
1289  m->setAngle( angle );
1290  m->setOffset( offset );
1291  return m;
1292 }