QGIS API Documentation  2.0.1-Dufour
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgslinesymbollayerv2.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslinesymbollayerv2.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 "qgslinesymbollayerv2.h"
17 #include "qgssymbollayerv2utils.h"
18 #include "qgsexpression.h"
19 #include "qgsrendercontext.h"
20 #include "qgslogger.h"
21 #include "qgsvectorlayer.h"
22 
23 #include <QPainter>
24 #include <QDomDocument>
25 #include <QDomElement>
26 
27 #include <cmath>
28 
29 QgsSimpleLineSymbolLayerV2::QgsSimpleLineSymbolLayerV2( QColor color, double width, Qt::PenStyle penStyle )
30  : mPenStyle( penStyle ), mPenJoinStyle( DEFAULT_SIMPLELINE_JOINSTYLE ), mPenCapStyle( DEFAULT_SIMPLELINE_CAPSTYLE ), mOffset( 0 ), mOffsetUnit( QgsSymbolV2::MM ),
31  mUseCustomDashPattern( false ), mCustomDashPatternUnit( QgsSymbolV2::MM )
32 {
33  mColor = color;
34  mWidth = width;
35  mCustomDashVector << 5 << 2;
36 }
37 
39 {
40  mWidthUnit = unit;
41  mOffsetUnit = unit;
43 }
44 
46 {
48  if ( mOffsetUnit != unit || mCustomDashPatternUnit != unit )
49  {
50  return QgsSymbolV2::Mixed;
51  }
52  return unit;
53 }
54 
55 
57 {
61 
62  if ( props.contains( "color" ) )
63  color = QgsSymbolLayerV2Utils::decodeColor( props["color"] );
64  if ( props.contains( "width" ) )
65  width = props["width"].toDouble();
66  if ( props.contains( "penstyle" ) )
67  penStyle = QgsSymbolLayerV2Utils::decodePenStyle( props["penstyle"] );
68 
69 
70  QgsSimpleLineSymbolLayerV2* l = new QgsSimpleLineSymbolLayerV2( color, width, penStyle );
71  if ( props.contains( "width_unit" ) )
72  l->setWidthUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["width_unit"] ) );
73  if ( props.contains( "offset" ) )
74  l->setOffset( props["offset"].toDouble() );
75  if ( props.contains( "offset_unit" ) )
76  l->setOffsetUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["offset_unit"] ) );
77  if ( props.contains( "joinstyle" ) )
79  if ( props.contains( "capstyle" ) )
81 
82  if ( props.contains( "use_custom_dash" ) )
83  {
84  l->setUseCustomDashPattern( props["use_custom_dash"].toInt() );
85  }
86  if ( props.contains( "customdash" ) )
87  {
89  }
90  if ( props.contains( "customdash_unit" ) )
91  {
92  l->setCustomDashPatternUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["customdash_unit"] ) );
93  }
94 
95  //data defined properties
96  if ( props.contains( "color_expression" ) )
97  l->setDataDefinedProperty( "color", props["color_expression"] );
98  if ( props.contains( "width_expression" ) )
99  l->setDataDefinedProperty( "width", props["width_expression"] );
100  if ( props.contains( "offset_expression" ) )
101  l->setDataDefinedProperty( "offset", props["offset_expression"] );
102  if ( props.contains( "customdash_expression" ) )
103  l->setDataDefinedProperty( "customdash", props["customdash_expression"] );
104  if ( props.contains( "joinstyle_expression" ) )
105  l->setDataDefinedProperty( "joinstyle", props["joinstyle_expression"] );
106  if ( props.contains( "capstyle_expression" ) )
107  l->setDataDefinedProperty( "capstyle", props["capstyle_expression"] );
108 
109  return l;
110 }
111 
112 
114 {
115  return "SimpleLine";
116 }
117 
119 {
120  QColor penColor = mColor;
121  penColor.setAlphaF( mColor.alphaF() * context.alpha() );
122  mPen.setColor( penColor );
123  double scaledWidth = mWidth * QgsSymbolLayerV2Utils::lineWidthScaleFactor( context.renderContext(), mWidthUnit );
124  mPen.setWidthF( scaledWidth );
125  if ( mUseCustomDashPattern && scaledWidth != 0 )
126  {
127  mPen.setStyle( Qt::CustomDashLine );
128 
129  //scale pattern vector
130  double dashWidthDiv = scaledWidth;
131  //fix dash pattern width in Qt 4.8
132  QStringList versionSplit = QString( qVersion() ).split( "." );
133  if ( versionSplit.size() > 1
134  && versionSplit.at( 1 ).toInt() >= 8
135  && ( scaledWidth * context.renderContext().rasterScaleFactor() ) < 1.0 )
136  {
137  dashWidthDiv = 1.0;
138  }
139  QVector<qreal> scaledVector;
140  QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
141  for ( ; it != mCustomDashVector.constEnd(); ++it )
142  {
143  //the dash is specified in terms of pen widths, therefore the division
144  scaledVector << ( *it ) * QgsSymbolLayerV2Utils::lineWidthScaleFactor( context.renderContext(), mCustomDashPatternUnit ) / dashWidthDiv;
145  }
146  mPen.setDashPattern( scaledVector );
147  }
148  else
149  {
150  mPen.setStyle( mPenStyle );
151  }
152  mPen.setJoinStyle( mPenJoinStyle );
153  mPen.setCapStyle( mPenCapStyle );
154 
155  mSelPen = mPen;
156  QColor selColor = context.renderContext().selectionColor();
157  if ( ! selectionIsOpaque )
158  selColor.setAlphaF( context.alpha() );
159  mSelPen.setColor( selColor );
160 
161  //prepare expressions for data defined properties
162  prepareExpressions( context.layer() );
163 }
164 
166 {
167  Q_UNUSED( context );
168 }
169 
171 {
172  QPainter* p = context.renderContext().painter();
173  if ( !p )
174  {
175  return;
176  }
177 
178  double offset = 0.0;
179  applyDataDefinedSymbology( context, mPen, mSelPen, offset );
180 
181  p->setPen( context.selected() ? mSelPen : mPen );
182 
183  if ( offset == 0 )
184  {
185  p->drawPolyline( points );
186  }
187  else
188  {
189  double scaledOffset = offset * QgsSymbolLayerV2Utils::lineWidthScaleFactor( context.renderContext(), mOffsetUnit );
190  p->drawPolyline( ::offsetLine( points, scaledOffset ) );
191  }
192 }
193 
195 {
196  QgsStringMap map;
197  map["color"] = QgsSymbolLayerV2Utils::encodeColor( mColor );
198  map["width"] = QString::number( mWidth );
203  map["offset"] = QString::number( mOffset );
205  map["use_custom_dash"] = ( mUseCustomDashPattern ? "1" : "0" );
209  return map;
210 }
211 
213 {
215  l->setWidthUnit( mWidthUnit );
218  l->setOffset( mOffset );
224  return l;
225 }
226 
227 void QgsSimpleLineSymbolLayerV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const
228 {
229  if ( mPenStyle == Qt::NoPen )
230  return;
231 
232  QDomElement symbolizerElem = doc.createElement( "se:LineSymbolizer" );
233  if ( !props.value( "uom", "" ).isEmpty() )
234  symbolizerElem.setAttribute( "uom", props.value( "uom", "" ) );
235  element.appendChild( symbolizerElem );
236 
237  // <Geometry>
238  QgsSymbolLayerV2Utils::createGeometryElement( doc, symbolizerElem, props.value( "geom", "" ) );
239 
240  // <Stroke>
241  QDomElement strokeElem = doc.createElement( "se:Stroke" );
242  symbolizerElem.appendChild( strokeElem );
243 
244  Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
245  QgsSymbolLayerV2Utils::lineToSld( doc, strokeElem, penStyle, mColor, mWidth,
247 
248  // <se:PerpendicularOffset>
249  if ( mOffset != 0 )
250  {
251  QDomElement perpOffsetElem = doc.createElement( "se:PerpendicularOffset" );
252  perpOffsetElem.appendChild( doc.createTextNode( QString::number( mOffset ) ) );
253  symbolizerElem.appendChild( perpOffsetElem );
254  }
255 }
256 
257 QString QgsSimpleLineSymbolLayerV2::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
258 {
259  if ( mUseCustomDashPattern )
260  {
261  return QgsSymbolLayerV2Utils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
262  mPen.color(), mPenJoinStyle,
264  }
265  else
266  {
267  return QgsSymbolLayerV2Utils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
269  }
270 }
271 
273 {
274  QgsDebugMsg( "Entered." );
275 
276  QDomElement strokeElem = element.firstChildElement( "Stroke" );
277  if ( strokeElem.isNull() )
278  return NULL;
279 
280  Qt::PenStyle penStyle;
281  QColor color;
282  double width;
283  Qt::PenJoinStyle penJoinStyle;
284  Qt::PenCapStyle penCapStyle;
285  QVector<qreal> customDashVector;
286 
287  if ( !QgsSymbolLayerV2Utils::lineFromSld( strokeElem, penStyle,
288  color, width,
289  &penJoinStyle, &penCapStyle,
290  &customDashVector ) )
291  return NULL;
292 
293  double offset = 0.0;
294  QDomElement perpOffsetElem = element.firstChildElement( "PerpendicularOffset" );
295  if ( !perpOffsetElem.isNull() )
296  {
297  bool ok;
298  double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
299  if ( ok )
300  offset = d;
301  }
302 
303  QgsSimpleLineSymbolLayerV2* l = new QgsSimpleLineSymbolLayerV2( color, width, penStyle );
304  l->setOffset( offset );
305  l->setPenJoinStyle( penJoinStyle );
306  l->setPenCapStyle( penCapStyle );
307  l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
308  l->setCustomDashVector( customDashVector );
309  return l;
310 }
311 
312 void QgsSimpleLineSymbolLayerV2::applyDataDefinedSymbology( QgsSymbolV2RenderContext& context, QPen& pen, QPen& selPen, double& offset )
313 {
314  //data defined properties
315  double scaledWidth = 0;
316  QgsExpression* strokeWidthExpression = expression( "width" );
317  if ( strokeWidthExpression )
318  {
319  scaledWidth = strokeWidthExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble()
321  pen.setWidthF( scaledWidth );
322  selPen.setWidthF( scaledWidth );
323  }
324  else if ( context.renderHints() & QgsSymbolV2::DataDefinedSizeScale )
325  {
327  pen.setWidthF( scaledWidth );
328  selPen.setWidthF( scaledWidth );
329  }
330 
331  //color
332  QgsExpression* strokeColorExpression = expression( "color" );
333  if ( strokeColorExpression )
334  {
335  pen.setColor( QgsSymbolLayerV2Utils::decodeColor( strokeColorExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString() ) );
336  }
337 
338  //offset
339  offset = mOffset;
340  QgsExpression* lineOffsetExpression = expression( "offset" );
341  if ( lineOffsetExpression )
342  {
343  offset = lineOffsetExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
344  }
345 
346  //dash dot vector
347  QgsExpression* dashPatternExpression = expression( "customdash" );
348  if ( dashPatternExpression )
349  {
350  QVector<qreal> dashVector;
351  QStringList dashList = dashPatternExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString().split( ";" );
352  QStringList::const_iterator dashIt = dashList.constBegin();
353  for ( ; dashIt != dashList.constEnd(); ++dashIt )
354  {
355  dashVector.push_back( dashIt->toDouble() * QgsSymbolLayerV2Utils::lineWidthScaleFactor( context.renderContext(), mCustomDashPatternUnit ) / mPen.widthF() );
356  }
357  pen.setDashPattern( dashVector );
358  }
359 
360  //join style
361  QgsExpression* joinStyleExpression = expression( "joinstyle" );
362  if ( joinStyleExpression )
363  {
364  QString joinStyleString = joinStyleExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString();
365  pen.setJoinStyle( QgsSymbolLayerV2Utils::decodePenJoinStyle( joinStyleString ) );
366  }
367 
368  //cap style
369  QgsExpression* capStyleExpression = expression( "capstyle" );
370  if ( capStyleExpression )
371  {
372  QString capStyleString = capStyleExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString();
373  pen.setCapStyle( QgsSymbolLayerV2Utils::decodePenCapStyle( capStyleString ) );
374  }
375 }
376 
377 
378 
380 
381 
382 class MyLine
383 {
384  public:
385  MyLine( QPointF p1, QPointF p2 ) : mVertical( false ), mIncreasing( false ), mT( 0.0 ), mLength( 0.0 )
386  {
387  if ( p1 == p2 )
388  return; // invalid
389 
390  // tangent and direction
391  if ( p1.x() == p2.x() )
392  {
393  // vertical line - tangent undefined
394  mVertical = true;
395  mIncreasing = ( p2.y() > p1.y() );
396  }
397  else
398  {
399  mVertical = false;
400  mT = float( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
401  mIncreasing = ( p2.x() > p1.x() );
402  }
403 
404  // length
405  double x = ( p2.x() - p1.x() );
406  double y = ( p2.y() - p1.y() );
407  mLength = sqrt( x * x + y * y );
408  }
409 
410  // return angle in radians
411  double angle()
412  {
413  double a = ( mVertical ? M_PI / 2 : atan( mT ) );
414 
415  if ( !mIncreasing )
416  a += M_PI;
417  return a;
418  }
419 
420  // return difference for x,y when going along the line with specified interval
421  QPointF diffForInterval( double interval )
422  {
423  if ( mVertical )
424  return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
425 
426  double alpha = atan( mT );
427  double dx = cos( alpha ) * interval;
428  double dy = sin( alpha ) * interval;
429  return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
430  }
431 
432  double length() { return mLength; }
433 
434  protected:
435  bool mVertical;
437  double mT;
438  double mLength;
439 };
440 
441 
442 QgsMarkerLineSymbolLayerV2::QgsMarkerLineSymbolLayerV2( bool rotateMarker, double interval )
443 {
447  mMarker = NULL;
448  mOffset = 0;
451 
453 }
454 
456 {
457  delete mMarker;
458 }
459 
461 {
462  bool rotate = DEFAULT_MARKERLINE_ROTATE;
464 
465  if ( props.contains( "interval" ) )
466  interval = props["interval"].toDouble();
467  if ( props.contains( "rotate" ) )
468  rotate = ( props["rotate"] == "1" );
469 
470  QgsMarkerLineSymbolLayerV2* x = new QgsMarkerLineSymbolLayerV2( rotate, interval );
471  if ( props.contains( "offset" ) )
472  {
473  x->setOffset( props["offset"].toDouble() );
474  }
475  if ( props.contains( "offset_unit" ) )
476  {
477  x->setOffsetUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["offset_unit"] ) );
478  }
479  if ( props.contains( "interval_unit" ) )
480  {
481  x->setIntervalUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["interval_unit"] ) );
482  }
483 
484  if ( props.contains( "placement" ) )
485  {
486  if ( props["placement"] == "vertex" )
487  x->setPlacement( Vertex );
488  else if ( props["placement"] == "lastvertex" )
489  x->setPlacement( LastVertex );
490  else if ( props["placement"] == "firstvertex" )
492  else if ( props["placement"] == "centralpoint" )
494  else
495  x->setPlacement( Interval );
496  }
497 
498  //data defined properties
499  if ( props.contains( "interval_expression" ) )
500  {
501  x->setDataDefinedProperty( "interval", props["interval_expression"] );
502  }
503  if ( props.contains( "offset_expression" ) )
504  {
505  x->setDataDefinedProperty( "offset", props["offset_expression"] );
506  }
507  if ( props.contains( "placement_expression" ) )
508  {
509  x->setDataDefinedProperty( "placement", props["placement_expression"] );
510  }
511 
512  return x;
513 }
514 
516 {
517  return "MarkerLine";
518 }
519 
520 void QgsMarkerLineSymbolLayerV2::setColor( const QColor& color )
521 {
522  mMarker->setColor( color );
523  mColor = color;
524 }
525 
527 {
528  mMarker->setAlpha( context.alpha() );
529 
530  // if being rotated, it gets initialized with every line segment
531  int hints = 0;
532  if ( mRotateMarker )
536  mMarker->setRenderHints( hints );
537 
538  mMarker->startRender( context.renderContext(), context.layer() );
539 
540  //prepare expressions for data defined properties
541  prepareExpressions( context.layer() );
542 }
543 
545 {
546  mMarker->stopRender( context.renderContext() );
547 }
548 
550 {
551  double offset = mOffset;
552  QgsExpression* offsetExpression = expression( "offset" );
553  if ( offsetExpression )
554  {
555  offset = offsetExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
556  }
557 
559  QgsExpression* placementExpression = expression( "placement" );
560  if ( placementExpression )
561  {
562  QString placementString = placementExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString();
563  if ( placementString.compare( "vertex", Qt::CaseInsensitive ) == 0 )
564  {
565  placement = Vertex;
566  }
567  else if ( placementString.compare( "lastvertex", Qt::CaseInsensitive ) == 0 )
568  {
569  placement = LastVertex;
570  }
571  else if ( placementString.compare( "firstvertex", Qt::CaseInsensitive ) == 0 )
572  {
573  placement = FirstVertex;
574  }
575  else if ( placementString.compare( "centerpoint", Qt::CaseInsensitive ) == 0 )
576  {
577  placement = CentralPoint;
578  }
579  else
580  {
581  placement = Interval;
582  }
583  }
584 
585  if ( offset == 0 )
586  {
587  if ( placement == Interval )
588  renderPolylineInterval( points, context );
589  else if ( placement == CentralPoint )
590  renderPolylineCentral( points, context );
591  else
592  renderPolylineVertex( points, context, placement );
593  }
594  else
595  {
596  QPolygonF points2 = ::offsetLine( points, offset * QgsSymbolLayerV2Utils::lineWidthScaleFactor( context.renderContext(), mOffsetUnit ) );
597  if ( placement == Interval )
598  renderPolylineInterval( points2, context );
599  else if ( placement == CentralPoint )
600  renderPolylineCentral( points2, context );
601  else
602  renderPolylineVertex( points2, context, placement );
603  }
604 }
605 
607 {
608  if ( points.isEmpty() )
609  return;
610 
611  QPointF lastPt = points[0];
612  double lengthLeft = 0; // how much is left until next marker
613  bool first = true;
614  double origAngle = mMarker->angle();
615 
616  QgsRenderContext& rc = context.renderContext();
617  double interval = mInterval;
618 
619  QgsExpression* intervalExpression = expression( "interval" );
620  if ( intervalExpression )
621  {
622  interval = intervalExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
623  }
624  if ( interval <= 0 )
625  {
626  interval = 0.1;
627  }
628 
629  double painterUnitInterval = interval * QgsSymbolLayerV2Utils::lineWidthScaleFactor( rc, mIntervalUnit );
630 
631  for ( int i = 1; i < points.count(); ++i )
632  {
633  const QPointF& pt = points[i];
634 
635  if ( lastPt == pt ) // must not be equal!
636  continue;
637 
638  // for each line, find out dx and dy, and length
639  MyLine l( lastPt, pt );
640  QPointF diff = l.diffForInterval( painterUnitInterval );
641 
642  // if there's some length left from previous line
643  // use only the rest for the first point in new line segment
644  double c = 1 - lengthLeft / painterUnitInterval;
645 
646  lengthLeft += l.length();
647 
648  // rotate marker (if desired)
649  if ( mRotateMarker )
650  {
651  mMarker->setAngle( origAngle + ( l.angle() * 180 / M_PI ) );
652  }
653 
654  // draw first marker
655  if ( first )
656  {
657  mMarker->renderPoint( lastPt, context.feature(), rc, -1, context.selected() );
658  first = false;
659  }
660 
661  // while we're not at the end of line segment, draw!
662  while ( lengthLeft > painterUnitInterval )
663  {
664  // "c" is 1 for regular point or in interval (0,1] for begin of line segment
665  lastPt += c * diff;
666  lengthLeft -= painterUnitInterval;
667  mMarker->renderPoint( lastPt, context.feature(), rc, -1, context.selected() );
668  c = 1; // reset c (if wasn't 1 already)
669  }
670 
671  lastPt = pt;
672  }
673 
674  // restore original rotation
675  mMarker->setAngle( origAngle );
676 
677 }
678 
679 static double _averageAngle( const QPointF& prevPt, const QPointF& pt, const QPointF& nextPt )
680 {
681  // calc average angle between the previous and next point
682  double a1 = MyLine( prevPt, pt ).angle();
683  double a2 = MyLine( pt, nextPt ).angle();
684  double unitX = cos( a1 ) + cos( a2 ), unitY = sin( a1 ) + sin( a2 );
685 
686  return atan2( unitY, unitX );
687 }
688 
689 void QgsMarkerLineSymbolLayerV2::renderPolylineVertex( const QPolygonF& points, QgsSymbolV2RenderContext& context, Placement placement )
690 {
691  if ( points.isEmpty() )
692  return;
693 
694  QgsRenderContext& rc = context.renderContext();
695 
696  double origAngle = mMarker->angle();
697  int i, maxCount;
698  bool isRing = false;
699 
700  if ( placement == FirstVertex )
701  {
702  i = 0;
703  maxCount = 1;
704  }
705  else if ( placement == LastVertex )
706  {
707  i = points.count() - 1;
708  maxCount = points.count();
709  }
710  else
711  {
712  i = 0;
713  maxCount = points.count();
714  if ( points.first() == points.last() )
715  isRing = true;
716  }
717 
718  for ( ; i < maxCount; ++i )
719  {
720  if ( isRing && placement == Vertex && i == points.count() - 1 )
721  {
722  continue; // don't draw the last marker - it has been drawn already
723  }
724  // rotate marker (if desired)
725  if ( mRotateMarker )
726  {
727  double angle = markerAngle( points, isRing, i );
728  mMarker->setAngle( origAngle + angle * 180 / M_PI );
729  }
730 
731  mMarker->renderPoint( points.at( i ), context.feature(), rc, -1, context.selected() );
732  }
733 
734  // restore original rotation
735  mMarker->setAngle( origAngle );
736 }
737 
738 double QgsMarkerLineSymbolLayerV2::markerAngle( const QPolygonF& points, bool isRing, int vertex )
739 {
740  double angle = 0;
741  const QPointF& pt = points[vertex];
742 
743  if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
744  {
745  int prevIndex = vertex - 1;
746  int nextIndex = vertex + 1;
747 
748  if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
749  {
750  prevIndex = points.count() - 2;
751  nextIndex = 1;
752  }
753 
754  QPointF prevPoint, nextPoint;
755  while ( prevIndex >= 0 )
756  {
757  prevPoint = points[ prevIndex ];
758  if ( prevPoint != pt )
759  {
760  break;
761  }
762  --prevIndex;
763  }
764 
765  while ( nextIndex < points.count() )
766  {
767  nextPoint = points[ nextIndex ];
768  if ( nextPoint != pt )
769  {
770  break;
771  }
772  ++nextIndex;
773  }
774 
775  if ( prevIndex >= 0 && nextIndex < points.count() )
776  {
777  angle = _averageAngle( prevPoint, pt, nextPoint );
778  }
779  }
780  else //no ring and vertex is at start / at end
781  {
782  if ( vertex == 0 )
783  {
784  while ( vertex < points.size() - 1 )
785  {
786  const QPointF& nextPt = points[vertex+1];
787  if ( pt != nextPt )
788  {
789  angle = MyLine( pt, nextPt ).angle();
790  return angle;
791  }
792  ++vertex;
793  }
794  }
795  else
796  {
797  // use last segment's angle
798  while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
799  {
800  const QPointF& prevPt = points[vertex-1];
801  if ( pt != prevPt )
802  {
803  angle = MyLine( prevPt, pt ).angle();
804  return angle;
805  }
806  --vertex;
807  }
808  }
809  }
810  return angle;
811 }
812 
814 {
815  if ( points.size() > 0 )
816  {
817  // calc length
818  qreal length = 0;
819  QPolygonF::const_iterator it = points.constBegin();
820  QPointF last = *it;
821  for ( ++it; it != points.constEnd(); ++it )
822  {
823  length += sqrt(( last.x() - it->x() ) * ( last.x() - it->x() ) +
824  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
825  last = *it;
826  }
827 
828  // find the segment where the central point lies
829  it = points.constBegin();
830  last = *it;
831  qreal last_at = 0, next_at = 0;
832  QPointF next;
833  int segment = 0;
834  for ( ++it; it != points.constEnd(); ++it )
835  {
836  next = *it;
837  next_at += sqrt(( last.x() - it->x() ) * ( last.x() - it->x() ) +
838  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
839  if ( next_at >= length / 2 )
840  break; // we have reached the center
841  last = *it;
842  last_at = next_at;
843  segment++;
844  }
845 
846  // find out the central point on segment
847  MyLine l( last, next ); // for line angle
848  qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
849  QPointF pt = last + ( next - last ) * k;
850 
851  // draw the marker
852  double origAngle = mMarker->angle();
853  if ( mRotateMarker )
854  mMarker->setAngle( origAngle + l.angle() * 180 / M_PI );
855  mMarker->renderPoint( pt, context.feature(), context.renderContext(), -1, context.selected() );
856  if ( mRotateMarker )
857  mMarker->setAngle( origAngle );
858  }
859 }
860 
861 
863 {
864  QgsStringMap map;
865  map["rotate"] = ( mRotateMarker ? "1" : "0" );
866  map["interval"] = QString::number( mInterval );
867  map["offset"] = QString::number( mOffset );
869  map["interval_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mIntervalUnit );
870  if ( mPlacement == Vertex )
871  map["placement"] = "vertex";
872  else if ( mPlacement == LastVertex )
873  map["placement"] = "lastvertex";
874  else if ( mPlacement == FirstVertex )
875  map["placement"] = "firstvertex";
876  else if ( mPlacement == CentralPoint )
877  map["placement"] = "centralpoint";
878  else
879  map["placement"] = "interval";
880 
882  return map;
883 }
884 
886 {
887  return mMarker;
888 }
889 
891 {
892  if ( symbol == NULL || symbol->type() != QgsSymbolV2::Marker )
893  {
894  delete symbol;
895  return false;
896  }
897 
898  delete mMarker;
899  mMarker = static_cast<QgsMarkerSymbolV2*>( symbol );
900  mColor = mMarker->color();
901  return true;
902 }
903 
905 {
907  x->setSubSymbol( mMarker->clone() );
908  x->setOffset( mOffset );
909  x->setPlacement( mPlacement );
913  return x;
914 }
915 
916 void QgsMarkerLineSymbolLayerV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const
917 {
918  for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
919  {
920  QDomElement symbolizerElem = doc.createElement( "se:LineSymbolizer" );
921  if ( !props.value( "uom", "" ).isEmpty() )
922  symbolizerElem.setAttribute( "uom", props.value( "uom", "" ) );
923  element.appendChild( symbolizerElem );
924 
925  // <Geometry>
926  QgsSymbolLayerV2Utils::createGeometryElement( doc, symbolizerElem, props.value( "geom", "" ) );
927 
928  QString gap;
929  switch ( mPlacement )
930  {
931  case FirstVertex:
932  symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "placement", "firstPoint" ) );
933  break;
934  case LastVertex:
935  symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "placement", "lastPoint" ) );
936  break;
937  case CentralPoint:
938  symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "placement", "centralPoint" ) );
939  break;
940  case Vertex:
941  // no way to get line/polygon's vertices, use a VendorOption
942  symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "placement", "points" ) );
943  break;
944  default:
945  gap = QString::number( mInterval );
946  break;
947  }
948 
949  if ( !mRotateMarker )
950  {
951  // markers in LineSymbolizer must be drawn following the line orientation,
952  // use a VendorOption when no marker rotation
953  symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "rotateMarker", "0" ) );
954  }
955 
956  // <Stroke>
957  QDomElement strokeElem = doc.createElement( "se:Stroke" );
958  symbolizerElem.appendChild( strokeElem );
959 
960  // <GraphicStroke>
961  QDomElement graphicStrokeElem = doc.createElement( "se:GraphicStroke" );
962  strokeElem.appendChild( graphicStrokeElem );
963 
964  QgsSymbolLayerV2 *layer = mMarker->symbolLayer( i );
965  QgsMarkerSymbolLayerV2 *markerLayer = static_cast<QgsMarkerSymbolLayerV2 *>( layer );
966  if ( !markerLayer )
967  {
968  graphicStrokeElem.appendChild( doc.createComment( QString( "MarkerSymbolLayerV2 expected, %1 found. Skip it." ).arg( markerLayer->layerType() ) ) );
969  }
970  else
971  {
972  markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
973  }
974 
975  if ( !gap.isEmpty() )
976  {
977  QDomElement gapElem = doc.createElement( "se:Gap" );
979  graphicStrokeElem.appendChild( gapElem );
980  }
981 
982  if ( !qgsDoubleNear( mOffset, 0.0 ) )
983  {
984  QDomElement perpOffsetElem = doc.createElement( "se:PerpendicularOffset" );
985  perpOffsetElem.appendChild( doc.createTextNode( QString::number( mOffset ) ) );
986  symbolizerElem.appendChild( perpOffsetElem );
987  }
988  }
989 }
990 
992 {
993  QgsDebugMsg( "Entered." );
994 
995  QDomElement strokeElem = element.firstChildElement( "Stroke" );
996  if ( strokeElem.isNull() )
997  return NULL;
998 
999  QDomElement graphicStrokeElem = strokeElem.firstChildElement( "GraphicStroke" );
1000  if ( graphicStrokeElem.isNull() )
1001  return NULL;
1002 
1003  // retrieve vendor options
1004  bool rotateMarker = true;
1006 
1007  QgsStringMap vendorOptions = QgsSymbolLayerV2Utils::getVendorOptionList( element );
1008  for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
1009  {
1010  if ( it.key() == "placement" )
1011  {
1012  if ( it.value() == "points" ) placement = Vertex;
1013  else if ( it.value() == "firstPoint" ) placement = FirstVertex;
1014  else if ( it.value() == "lastPoint" ) placement = LastVertex;
1015  else if ( it.value() == "centralPoint" ) placement = CentralPoint;
1016  }
1017  else if ( it.value() == "rotateMarker" )
1018  {
1019  rotateMarker = it.value() == "0";
1020  }
1021  }
1022 
1023  QgsMarkerSymbolV2 *marker = 0;
1024 
1026  if ( l )
1027  {
1028  QgsSymbolLayerV2List layers;
1029  layers.append( l );
1030  marker = new QgsMarkerSymbolV2( layers );
1031  }
1032 
1033  if ( !marker )
1034  return NULL;
1035 
1036  double interval = 0.0;
1037  QDomElement gapElem = graphicStrokeElem.firstChildElement( "Gap" );
1038  if ( !gapElem.isNull() )
1039  {
1040  bool ok;
1041  double d = gapElem.firstChild().nodeValue().toDouble( &ok );
1042  if ( ok )
1043  interval = d;
1044  }
1045 
1046  double offset = 0.0;
1047  QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( "PerpendicularOffset" );
1048  if ( !perpOffsetElem.isNull() )
1049  {
1050  bool ok;
1051  double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
1052  if ( ok )
1053  offset = d;
1054  }
1055 
1056  QgsMarkerLineSymbolLayerV2* x = new QgsMarkerLineSymbolLayerV2( rotateMarker );
1057  x->setPlacement( placement );
1058  x->setInterval( interval );
1059  x->setSubSymbol( marker );
1060  x->setOffset( offset );
1061  return x;
1062 }
1063 
1065 {
1066  mMarker->setSize( width );
1067 }
1068 
1070 {
1071  return mMarker->size();
1072 }
1073 
1075 {
1076  mIntervalUnit = unit;
1077  mOffsetUnit = unit;
1078 }
1079 
1081 {
1083  if ( mOffsetUnit != unit )
1084  {
1085  return QgsSymbolV2::Mixed;
1086  }
1087  return unit;
1088 }