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