QGIS API Documentation  2.8.2-Wien
 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 )
33  , mPenJoinStyle( DEFAULT_SIMPLELINE_JOINSTYLE )
34  , mPenCapStyle( DEFAULT_SIMPLELINE_CAPSTYLE )
35  , mUseCustomDashPattern( false )
36  , mCustomDashPatternUnit( QgsSymbolV2::MM )
37  , mDrawInsidePolygon( false )
38 {
39  mColor = color;
40  mWidth = width;
41  mCustomDashVector << 5 << 2;
42 }
43 
45 {
47  mWidthUnit = unit;
48  mOffsetUnit = unit;
50 }
51 
53 {
55  if ( mWidthUnit != unit || mOffsetUnit != unit || mCustomDashPatternUnit != unit )
56  {
57  return QgsSymbolV2::Mixed;
58  }
59  return unit;
60 }
61 
63 {
65  mWidthMapUnitScale = scale;
66  mOffsetMapUnitScale = scale;
68 }
69 
71 {
75  {
76  return mWidthMapUnitScale;
77  }
78  return QgsMapUnitScale();
79 }
80 
82 {
86 
87  if ( props.contains( "line_color" ) )
88  {
89  color = QgsSymbolLayerV2Utils::decodeColor( props["line_color"] );
90  }
91  else if ( props.contains( "outline_color" ) )
92  {
93  color = QgsSymbolLayerV2Utils::decodeColor( props["outline_color"] );
94  }
95  else if ( props.contains( "color" ) )
96  {
97  //pre 2.5 projects used "color"
98  color = QgsSymbolLayerV2Utils::decodeColor( props["color"] );
99  }
100  if ( props.contains( "line_width" ) )
101  {
102  width = props["line_width"].toDouble();
103  }
104  else if ( props.contains( "outline_width" ) )
105  {
106  width = props["outline_width"].toDouble();
107  }
108  else if ( props.contains( "width" ) )
109  {
110  //pre 2.5 projects used "width"
111  width = props["width"].toDouble();
112  }
113  if ( props.contains( "line_style" ) )
114  {
115  penStyle = QgsSymbolLayerV2Utils::decodePenStyle( props["line_style"] );
116  }
117  else if ( props.contains( "outline_style" ) )
118  {
119  penStyle = QgsSymbolLayerV2Utils::decodePenStyle( props["outline_style"] );
120  }
121  else if ( props.contains( "penstyle" ) )
122  {
123  penStyle = QgsSymbolLayerV2Utils::decodePenStyle( props["penstyle"] );
124  }
125 
126  QgsSimpleLineSymbolLayerV2* l = new QgsSimpleLineSymbolLayerV2( color, width, penStyle );
127  if ( props.contains( "line_width_unit" ) )
128  {
129  l->setWidthUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["line_width_unit"] ) );
130  }
131  else if ( props.contains( "outline_width_unit" ) )
132  {
133  l->setWidthUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["outline_width_unit"] ) );
134  }
135  else if ( props.contains( "width_unit" ) )
136  {
137  //pre 2.5 projects used "width_unit"
138  l->setWidthUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["width_unit"] ) );
139  }
140  if ( props.contains( "width_map_unit_scale" ) )
141  l->setWidthMapUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["width_map_unit_scale"] ) );
142  if ( props.contains( "offset" ) )
143  l->setOffset( props["offset"].toDouble() );
144  if ( props.contains( "offset_unit" ) )
145  l->setOffsetUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["offset_unit"] ) );
146  if ( props.contains( "offset_map_unit_scale" ) )
147  l->setOffsetMapUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["offset_map_unit_scale"] ) );
148  if ( props.contains( "joinstyle" ) )
150  if ( props.contains( "capstyle" ) )
151  l->setPenCapStyle( QgsSymbolLayerV2Utils::decodePenCapStyle( props["capstyle"] ) );
152 
153  if ( props.contains( "use_custom_dash" ) )
154  {
155  l->setUseCustomDashPattern( props["use_custom_dash"].toInt() );
156  }
157  if ( props.contains( "customdash" ) )
158  {
160  }
161  if ( props.contains( "customdash_unit" ) )
162  {
163  l->setCustomDashPatternUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["customdash_unit"] ) );
164  }
165  if ( props.contains( "customdash_map_unit_scale" ) )
166  {
167  l->setCustomDashPatternMapUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["customdash_map_unit_scale"] ) );
168  }
169 
170  if ( props.contains( "draw_inside_polygon" ) )
171  {
172  l->setDrawInsidePolygon( props["draw_inside_polygon"].toInt() );
173  }
174 
175  //data defined properties
176  if ( props.contains( "color_expression" ) )
177  l->setDataDefinedProperty( "color", props["color_expression"] );
178  if ( props.contains( "width_expression" ) )
179  l->setDataDefinedProperty( "width", props["width_expression"] );
180  if ( props.contains( "offset_expression" ) )
181  l->setDataDefinedProperty( "offset", props["offset_expression"] );
182  if ( props.contains( "customdash_expression" ) )
183  l->setDataDefinedProperty( "customdash", props["customdash_expression"] );
184  if ( props.contains( "joinstyle_expression" ) )
185  l->setDataDefinedProperty( "joinstyle", props["joinstyle_expression"] );
186  if ( props.contains( "capstyle_expression" ) )
187  l->setDataDefinedProperty( "capstyle", props["capstyle_expression"] );
188  if ( props.contains( "line_style_expression" ) )
189  l->setDataDefinedProperty( "line_style", props["line_style_expression"] );
190 
191  return l;
192 }
193 
194 
196 {
197  return "SimpleLine";
198 }
199 
201 {
202  QColor penColor = mColor;
203  penColor.setAlphaF( mColor.alphaF() * context.alpha() );
204  mPen.setColor( penColor );
206  mPen.setWidthF( scaledWidth );
207  if ( mUseCustomDashPattern && scaledWidth != 0 )
208  {
209  mPen.setStyle( Qt::CustomDashLine );
210 
211  //scale pattern vector
212  double dashWidthDiv = scaledWidth;
213  //fix dash pattern width in Qt 4.8
214  QStringList versionSplit = QString( qVersion() ).split( "." );
215  if ( versionSplit.size() > 1
216  && versionSplit.at( 1 ).toInt() >= 8
217  && ( scaledWidth * context.renderContext().rasterScaleFactor() ) < 1.0 )
218  {
219  dashWidthDiv = 1.0;
220  }
221  QVector<qreal> scaledVector;
222  QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
223  for ( ; it != mCustomDashVector.constEnd(); ++it )
224  {
225  //the dash is specified in terms of pen widths, therefore the division
227  }
228  mPen.setDashPattern( scaledVector );
229  }
230  else
231  {
232  mPen.setStyle( mPenStyle );
233  }
234  mPen.setJoinStyle( mPenJoinStyle );
235  mPen.setCapStyle( mPenCapStyle );
236 
237  mSelPen = mPen;
238  QColor selColor = context.renderContext().selectionColor();
239  if ( ! selectionIsOpaque )
240  selColor.setAlphaF( context.alpha() );
241  mSelPen.setColor( selColor );
242 
243  //prepare expressions for data defined properties
244  prepareExpressions( context.fields(), context.renderContext().rendererScale() );
245 }
246 
248 {
249  Q_UNUSED( context );
250 }
251 
252 void QgsSimpleLineSymbolLayerV2::renderPolygonOutline( const QPolygonF& points, QList<QPolygonF>* rings, QgsSymbolV2RenderContext& context )
253 {
254  QPainter* p = context.renderContext().painter();
255  if ( !p )
256  {
257  return;
258  }
259 
260  if ( mDrawInsidePolygon )
261  {
262  //only drawing the line on the interior of the polygon, so set clip path for painter
263  p->save();
264  QPainterPath clipPath;
265  clipPath.addPolygon( points );
266 
267  if ( rings != NULL )
268  {
269  //add polygon rings
270  QList<QPolygonF>::const_iterator it = rings->constBegin();
271  for ( ; it != rings->constEnd(); ++it )
272  {
273  QPolygonF ring = *it;
274  clipPath.addPolygon( ring );
275  }
276  }
277 
278  //use intersect mode, as a clip path may already exist (eg, for composer maps)
279  p->setClipPath( clipPath, Qt::IntersectClip );
280  }
281 
282  renderPolyline( points, context );
283  if ( rings )
284  {
285  mOffset = -mOffset; // invert the offset for rings!
286  foreach ( const QPolygonF& ring, *rings )
287  renderPolyline( ring, context );
288  mOffset = -mOffset;
289  }
290 
291  if ( mDrawInsidePolygon )
292  {
293  //restore painter to reset clip path
294  p->restore();
295  }
296 
297 }
298 
300 {
301  QPainter* p = context.renderContext().painter();
302  if ( !p )
303  {
304  return;
305  }
306 
307  //size scaling by field
309  {
310  applySizeScale( context, mPen, mSelPen );
311  }
312 
313  double offset = mOffset;
314  applyDataDefinedSymbology( context, mPen, mSelPen, offset );
315 
316  p->setPen( context.selected() ? mSelPen : mPen );
317 
318  // Disable 'Antialiasing' if the geometry was generalized in the current RenderContext (We known that it must have least #2 points).
319  if ( points.size() <= 2 &&
322  ( p->renderHints() & QPainter::Antialiasing ) )
323  {
324  p->setRenderHint( QPainter::Antialiasing, false );
325  p->drawPolyline( points );
326  p->setRenderHint( QPainter::Antialiasing, true );
327  return;
328  }
329 
330  if ( qgsDoubleNear( offset, 0 ) )
331  {
332  p->drawPolyline( points );
333  }
334  else
335  {
337  QList<QPolygonF> mline = ::offsetLine( points, scaledOffset, context.feature() ? context.feature()->geometry()->type() : QGis::Line );
338  for ( int part = 0; part < mline.count(); ++part )
339  p->drawPolyline( mline[ part ] );
340  }
341 }
342 
344 {
345  QgsStringMap map;
346  map["line_color"] = QgsSymbolLayerV2Utils::encodeColor( mColor );
347  map["line_width"] = QString::number( mWidth );
348  map["line_width_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mWidthUnit );
349  map["width_map_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( mWidthMapUnitScale );
350  map["line_style"] = QgsSymbolLayerV2Utils::encodePenStyle( mPenStyle );
353  map["offset"] = QString::number( mOffset );
355  map["offset_map_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( mOffsetMapUnitScale );
356  map["use_custom_dash"] = ( mUseCustomDashPattern ? "1" : "0" );
360  map["draw_inside_polygon"] = ( mDrawInsidePolygon ? "1" : "0" );
362  return map;
363 }
364 
366 {
368  l->setWidthUnit( mWidthUnit );
374  l->setOffset( mOffset );
381  return l;
382 }
383 
384 void QgsSimpleLineSymbolLayerV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const
385 {
386  if ( mPenStyle == Qt::NoPen )
387  return;
388 
389  QDomElement symbolizerElem = doc.createElement( "se:LineSymbolizer" );
390  if ( !props.value( "uom", "" ).isEmpty() )
391  symbolizerElem.setAttribute( "uom", props.value( "uom", "" ) );
392  element.appendChild( symbolizerElem );
393 
394  // <Geometry>
395  QgsSymbolLayerV2Utils::createGeometryElement( doc, symbolizerElem, props.value( "geom", "" ) );
396 
397  // <Stroke>
398  QDomElement strokeElem = doc.createElement( "se:Stroke" );
399  symbolizerElem.appendChild( strokeElem );
400 
401  Qt::PenStyle penStyle = mUseCustomDashPattern ? Qt::CustomDashLine : mPenStyle;
402  QgsSymbolLayerV2Utils::lineToSld( doc, strokeElem, penStyle, mColor, mWidth,
404 
405  // <se:PerpendicularOffset>
406  if ( mOffset != 0 )
407  {
408  QDomElement perpOffsetElem = doc.createElement( "se:PerpendicularOffset" );
409  perpOffsetElem.appendChild( doc.createTextNode( QString::number( mOffset ) ) );
410  symbolizerElem.appendChild( perpOffsetElem );
411  }
412 }
413 
414 QString QgsSimpleLineSymbolLayerV2::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
415 {
416  if ( mUseCustomDashPattern )
417  {
418  return QgsSymbolLayerV2Utils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor,
419  mPen.color(), mPenJoinStyle,
421  }
422  else
423  {
424  return QgsSymbolLayerV2Utils::ogrFeatureStylePen( mWidth, mmScaleFactor, mapUnitScaleFactor, mPen.color(), mPenJoinStyle,
426  }
427 }
428 
430 {
431  QgsDebugMsg( "Entered." );
432 
433  QDomElement strokeElem = element.firstChildElement( "Stroke" );
434  if ( strokeElem.isNull() )
435  return NULL;
436 
437  Qt::PenStyle penStyle;
438  QColor color;
439  double width;
440  Qt::PenJoinStyle penJoinStyle;
441  Qt::PenCapStyle penCapStyle;
442  QVector<qreal> customDashVector;
443 
444  if ( !QgsSymbolLayerV2Utils::lineFromSld( strokeElem, penStyle,
445  color, width,
446  &penJoinStyle, &penCapStyle,
447  &customDashVector ) )
448  return NULL;
449 
450  double offset = 0.0;
451  QDomElement perpOffsetElem = element.firstChildElement( "PerpendicularOffset" );
452  if ( !perpOffsetElem.isNull() )
453  {
454  bool ok;
455  double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
456  if ( ok )
457  offset = d;
458  }
459 
460  QgsSimpleLineSymbolLayerV2* l = new QgsSimpleLineSymbolLayerV2( color, width, penStyle );
461  l->setOffset( offset );
462  l->setPenJoinStyle( penJoinStyle );
463  l->setPenCapStyle( penCapStyle );
464  l->setUseCustomDashPattern( penStyle == Qt::CustomDashLine );
465  l->setCustomDashVector( customDashVector );
466  return l;
467 }
468 
469 void QgsSimpleLineSymbolLayerV2::applySizeScale( QgsSymbolV2RenderContext& context, QPen& pen, QPen& selPen )
470 {
472  pen.setWidthF( scaledWidth );
473  selPen.setWidthF( scaledWidth );
474 }
475 
476 void QgsSimpleLineSymbolLayerV2::applyDataDefinedSymbology( QgsSymbolV2RenderContext& context, QPen& pen, QPen& selPen, double& offset )
477 {
478  if ( mDataDefinedProperties.isEmpty() )
479  return; // shortcut
480 
481  //data defined properties
482  QgsExpression* strokeWidthExpression = expression( "width" );
483  if ( strokeWidthExpression )
484  {
485  double scaledWidth = strokeWidthExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble()
487  pen.setWidthF( scaledWidth );
488  selPen.setWidthF( scaledWidth );
489  }
490 
491  //color
492  QgsExpression* strokeColorExpression = expression( "color" );
493  if ( strokeColorExpression )
494  {
495  pen.setColor( QgsSymbolLayerV2Utils::decodeColor( strokeColorExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString() ) );
496  }
497 
498  //offset
499  QgsExpression* lineOffsetExpression = expression( "offset" );
500  if ( lineOffsetExpression )
501  {
502  offset = lineOffsetExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
503  }
504 
505  //dash dot vector
506  QgsExpression* dashPatternExpression = expression( "customdash" );
507  if ( dashPatternExpression )
508  {
510  double dashWidthDiv = mPen.widthF();
511 
512  if ( strokeWidthExpression )
513  {
514  dashWidthDiv = pen.widthF();
515  scaledWidth = pen.widthF();
516  }
517 
518  //fix dash pattern width in Qt 4.8
519  QStringList versionSplit = QString( qVersion() ).split( "." );
520  if ( versionSplit.size() > 1
521  && versionSplit.at( 1 ).toInt() >= 8
522  && ( scaledWidth * context.renderContext().rasterScaleFactor() ) < 1.0 )
523  {
524  dashWidthDiv = 1.0;
525  }
526 
527  QVector<qreal> dashVector;
528  QStringList dashList = dashPatternExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString().split( ";" );
529  QStringList::const_iterator dashIt = dashList.constBegin();
530  for ( ; dashIt != dashList.constEnd(); ++dashIt )
531  {
532  dashVector.push_back( dashIt->toDouble() * QgsSymbolLayerV2Utils::lineWidthScaleFactor( context.renderContext(), mCustomDashPatternUnit, mCustomDashPatternMapUnitScale ) / dashWidthDiv );
533  }
534  pen.setDashPattern( dashVector );
535  }
536 
537  //line style
538  QgsExpression* lineStyleExpression = expression( "line_style" );
539  if ( lineStyleExpression )
540  {
541  QString lineStyleString = lineStyleExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString();
542  pen.setStyle( QgsSymbolLayerV2Utils::decodePenStyle( lineStyleString ) );
543  }
544 
545  //join style
546  QgsExpression* joinStyleExpression = expression( "joinstyle" );
547  if ( joinStyleExpression )
548  {
549  QString joinStyleString = joinStyleExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString();
550  pen.setJoinStyle( QgsSymbolLayerV2Utils::decodePenJoinStyle( joinStyleString ) );
551  }
552 
553  //cap style
554  QgsExpression* capStyleExpression = expression( "capstyle" );
555  if ( capStyleExpression )
556  {
557  QString capStyleString = capStyleExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString();
558  pen.setCapStyle( QgsSymbolLayerV2Utils::decodePenCapStyle( capStyleString ) );
559  }
560 }
561 
563 {
564  if ( mDrawInsidePolygon )
565  {
566  //set to clip line to the interior of polygon, so we expect no bleed
567  return 0;
568  }
569  else
570  {
571  return ( mWidth / 2.0 ) + mOffset;
572  }
573 }
574 
576 {
577  unit = mCustomDashPatternUnit;
578  return mUseCustomDashPattern ? mCustomDashVector : QVector<qreal>();
579 }
580 
582 {
583  return mPenStyle;
584 }
585 
587 {
588  double width = mWidth;
589  QgsExpression* strokeWidthExpression = expression( "width" );
590  if ( strokeWidthExpression )
591  {
592  width = strokeWidthExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble() * e.mapUnitScaleFactor( e.symbologyScaleDenominator(), widthUnit(), e.mapUnits() );
593  }
594  else if ( context.renderHints() & QgsSymbolV2::DataDefinedSizeScale )
595  {
597  }
598 
599  return width * e.mapUnitScaleFactor( e.symbologyScaleDenominator(), widthUnit(), e.mapUnits() );
600 }
601 
603 {
604  QgsExpression* strokeColorExpression = expression( "color" );
605  if ( strokeColorExpression )
606  {
607  return ( QgsSymbolLayerV2Utils::decodeColor( strokeColorExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString() ) );
608  }
609  return mColor;
610 }
611 
613 {
614  Q_UNUSED( e );
615  double offset = mOffset;
616  QgsExpression* offsetExpression = expression( "offset" );
617  if ( offsetExpression )
618  {
619  offset = offsetExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
620  }
621  return offset;
622 }
623 
625 
626 
627 class MyLine
628 {
629  public:
630  MyLine( QPointF p1, QPointF p2 ) : mVertical( false ), mIncreasing( false ), mT( 0.0 ), mLength( 0.0 )
631  {
632  if ( p1 == p2 )
633  return; // invalid
634 
635  // tangent and direction
636  if ( p1.x() == p2.x() )
637  {
638  // vertical line - tangent undefined
639  mVertical = true;
640  mIncreasing = ( p2.y() > p1.y() );
641  }
642  else
643  {
644  mVertical = false;
645  mT = float( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
646  mIncreasing = ( p2.x() > p1.x() );
647  }
648 
649  // length
650  double x = ( p2.x() - p1.x() );
651  double y = ( p2.y() - p1.y() );
652  mLength = sqrt( x * x + y * y );
653  }
654 
655  // return angle in radians
656  double angle()
657  {
658  double a = ( mVertical ? M_PI / 2 : atan( mT ) );
659 
660  if ( !mIncreasing )
661  a += M_PI;
662  return a;
663  }
664 
665  // return difference for x,y when going along the line with specified interval
666  QPointF diffForInterval( double interval )
667  {
668  if ( mVertical )
669  return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
670 
671  double alpha = atan( mT );
672  double dx = cos( alpha ) * interval;
673  double dy = sin( alpha ) * interval;
674  return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
675  }
676 
677  double length() { return mLength; }
678 
679  protected:
680  bool mVertical;
682  double mT;
683  double mLength;
684 };
685 
686 
687 QgsMarkerLineSymbolLayerV2::QgsMarkerLineSymbolLayerV2( bool rotateMarker, double interval )
688 {
692  mMarker = NULL;
694  mOffsetAlongLine = 0;
696 
698 }
699 
701 {
702  delete mMarker;
703 }
704 
706 {
707  bool rotate = DEFAULT_MARKERLINE_ROTATE;
709 
710 
711  if ( props.contains( "interval" ) )
712  interval = props["interval"].toDouble();
713  if ( props.contains( "rotate" ) )
714  rotate = ( props["rotate"] == "1" );
715 
716  QgsMarkerLineSymbolLayerV2* x = new QgsMarkerLineSymbolLayerV2( rotate, interval );
717  if ( props.contains( "offset" ) )
718  {
719  x->setOffset( props["offset"].toDouble() );
720  }
721  if ( props.contains( "offset_unit" ) )
722  {
723  x->setOffsetUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["offset_unit"] ) );
724  }
725  if ( props.contains( "interval_unit" ) )
726  {
727  x->setIntervalUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["interval_unit"] ) );
728  }
729  if ( props.contains( "offset_along_line" ) )
730  {
731  x->setOffsetAlongLine( props["offset_along_line"].toDouble() );
732  }
733  if ( props.contains( "offset_along_line_unit" ) )
734  {
735  x->setOffsetAlongLineUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["offset_along_line_unit"] ) );
736  }
737  if ( props.contains(( "offset_along_line_map_unit_scale" ) ) )
738  {
739  x->setOffsetAlongLineMapUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["offset_along_line_map_unit_scale"] ) );
740  }
741 
742  if ( props.contains( "offset_map_unit_scale" ) )
743  {
744  x->setOffsetMapUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["offset_map_unit_scale"] ) );
745  }
746  if ( props.contains( "interval_map_unit_scale" ) )
747  {
748  x->setIntervalMapUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["interval_map_unit_scale"] ) );
749  }
750 
751  if ( props.contains( "placement" ) )
752  {
753  if ( props["placement"] == "vertex" )
754  x->setPlacement( Vertex );
755  else if ( props["placement"] == "lastvertex" )
756  x->setPlacement( LastVertex );
757  else if ( props["placement"] == "firstvertex" )
759  else if ( props["placement"] == "centralpoint" )
761  else
762  x->setPlacement( Interval );
763  }
764 
765  //data defined properties
766  if ( props.contains( "interval_expression" ) )
767  {
768  x->setDataDefinedProperty( "interval", props["interval_expression"] );
769  }
770  if ( props.contains( "offset_expression" ) )
771  {
772  x->setDataDefinedProperty( "offset", props["offset_expression"] );
773  }
774  if ( props.contains( "placement_expression" ) )
775  {
776  x->setDataDefinedProperty( "placement", props["placement_expression"] );
777  }
778  if ( props.contains( "offset_along_line_expression" ) )
779  {
780  x->setDataDefinedProperty( "offset_along_line", props["offset_along_line_expression"] );
781  }
782 
783  return x;
784 }
785 
787 {
788  return "MarkerLine";
789 }
790 
791 void QgsMarkerLineSymbolLayerV2::setColor( const QColor& color )
792 {
793  mMarker->setColor( color );
794  mColor = color;
795 }
796 
798 {
799  mMarker->setAlpha( context.alpha() );
800 
801  // if being rotated, it gets initialized with every line segment
802  int hints = 0;
803  if ( mRotateMarker )
807  mMarker->setRenderHints( hints );
808 
809  mMarker->startRender( context.renderContext(), context.fields() );
810 
811  //prepare expressions for data defined properties
812  prepareExpressions( context.fields(), context.renderContext().rendererScale() );
813 }
814 
816 {
817  mMarker->stopRender( context.renderContext() );
818 }
819 
821 {
822  double offset = mOffset;
823  QgsExpression* offsetExpression = expression( "offset" );
824  if ( offsetExpression )
825  {
826  offset = offsetExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
827  }
828 
830  QgsExpression* placementExpression = expression( "placement" );
831  if ( placementExpression )
832  {
833  QString placementString = placementExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toString();
834  if ( placementString.compare( "vertex", Qt::CaseInsensitive ) == 0 )
835  {
836  placement = Vertex;
837  }
838  else if ( placementString.compare( "lastvertex", Qt::CaseInsensitive ) == 0 )
839  {
840  placement = LastVertex;
841  }
842  else if ( placementString.compare( "firstvertex", Qt::CaseInsensitive ) == 0 )
843  {
844  placement = FirstVertex;
845  }
846  else if ( placementString.compare( "centerpoint", Qt::CaseInsensitive ) == 0 )
847  {
848  placement = CentralPoint;
849  }
850  else
851  {
852  placement = Interval;
853  }
854  }
855 
856  if ( offset == 0 )
857  {
858  if ( placement == Interval )
859  renderPolylineInterval( points, context );
860  else if ( placement == CentralPoint )
861  renderPolylineCentral( points, context );
862  else
863  renderPolylineVertex( points, context, placement );
864  }
865  else
866  {
867  QList<QPolygonF> mline = ::offsetLine( points, offset * QgsSymbolLayerV2Utils::lineWidthScaleFactor( context.renderContext(), mOffsetUnit, mOffsetMapUnitScale ), context.feature() ? context.feature()->geometry()->type() : QGis::Line );
868 
869  for ( int part = 0; part < mline.count(); ++part )
870  {
871  const QPolygonF &points2 = mline[ part ];
872 
873  if ( placement == Interval )
874  renderPolylineInterval( points2, context );
875  else if ( placement == CentralPoint )
876  renderPolylineCentral( points2, context );
877  else
878  renderPolylineVertex( points2, context, placement );
879  }
880  }
881 }
882 
883 void QgsMarkerLineSymbolLayerV2::renderPolygonOutline( const QPolygonF& points, QList<QPolygonF>* rings, QgsSymbolV2RenderContext& context )
884 {
885  renderPolyline( points, context );
886  if ( rings )
887  {
888  mOffset = -mOffset; // invert the offset for rings!
889  foreach ( const QPolygonF& ring, *rings )
890  renderPolyline( ring, context );
891  mOffset = -mOffset;
892  }
893 }
894 
896 {
897  if ( points.isEmpty() )
898  return;
899 
900  QPointF lastPt = points[0];
901  double lengthLeft = 0; // how much is left until next marker
902  bool first = mOffsetAlongLine ? false : true; //only draw marker at first vertex when no offset along line is set
903  double origAngle = mMarker->angle();
904 
905  QgsRenderContext& rc = context.renderContext();
906  double interval = mInterval;
907 
908  QgsExpression* intervalExpression = expression( "interval" );
909  if ( intervalExpression )
910  {
911  interval = intervalExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
912  }
913  if ( interval <= 0 )
914  {
915  interval = 0.1;
916  }
918  QgsExpression* offsetAlongLineExpression = expression( "offset_along_line" );
919  if ( offsetAlongLineExpression )
920  {
921  offsetAlongLine = offsetAlongLineExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
922  }
923 
924  double painterUnitInterval = interval * QgsSymbolLayerV2Utils::lineWidthScaleFactor( rc, mIntervalUnit, mIntervalMapUnitScale );
925  lengthLeft = painterUnitInterval - offsetAlongLine * QgsSymbolLayerV2Utils::lineWidthScaleFactor( rc, mIntervalUnit, mIntervalMapUnitScale );
926 
927  for ( int i = 1; i < points.count(); ++i )
928  {
929  const QPointF& pt = points[i];
930 
931  if ( lastPt == pt ) // must not be equal!
932  continue;
933 
934  // for each line, find out dx and dy, and length
935  MyLine l( lastPt, pt );
936  QPointF diff = l.diffForInterval( painterUnitInterval );
937 
938  // if there's some length left from previous line
939  // use only the rest for the first point in new line segment
940  double c = 1 - lengthLeft / painterUnitInterval;
941 
942  lengthLeft += l.length();
943 
944  // rotate marker (if desired)
945  if ( mRotateMarker )
946  {
947  mMarker->setAngle( origAngle + ( l.angle() * 180 / M_PI ) );
948  }
949 
950  // draw first marker
951  if ( first )
952  {
953  mMarker->renderPoint( lastPt, context.feature(), rc, -1, context.selected() );
954  first = false;
955  }
956 
957  // while we're not at the end of line segment, draw!
958  while ( lengthLeft > painterUnitInterval )
959  {
960  // "c" is 1 for regular point or in interval (0,1] for begin of line segment
961  lastPt += c * diff;
962  lengthLeft -= painterUnitInterval;
963  mMarker->renderPoint( lastPt, context.feature(), rc, -1, context.selected() );
964  c = 1; // reset c (if wasn't 1 already)
965  }
966 
967  lastPt = pt;
968  }
969 
970  // restore original rotation
971  mMarker->setAngle( origAngle );
972 
973 }
974 
975 static double _averageAngle( const QPointF& prevPt, const QPointF& pt, const QPointF& nextPt )
976 {
977  // calc average angle between the previous and next point
978  double a1 = MyLine( prevPt, pt ).angle();
979  double a2 = MyLine( pt, nextPt ).angle();
980  double unitX = cos( a1 ) + cos( a2 ), unitY = sin( a1 ) + sin( a2 );
981 
982  return atan2( unitY, unitX );
983 }
984 
985 void QgsMarkerLineSymbolLayerV2::renderPolylineVertex( const QPolygonF& points, QgsSymbolV2RenderContext& context, Placement placement )
986 {
987  if ( points.isEmpty() )
988  return;
989 
990  QgsRenderContext& rc = context.renderContext();
991 
992  double origAngle = mMarker->angle();
993  int i, maxCount;
994  bool isRing = false;
995 
997  QgsExpression* offsetAlongLineExpression = expression( "offset_along_line" );
998  if ( offsetAlongLineExpression )
999  {
1000  offsetAlongLine = offsetAlongLineExpression->evaluate( const_cast<QgsFeature*>( context.feature() ) ).toDouble();
1001  }
1002  if ( offsetAlongLine != 0 )
1003  {
1004  //scale offset along line
1006  }
1007 
1008  if ( placement == FirstVertex )
1009  {
1010  i = 0;
1011  maxCount = 1;
1012  }
1013  else if ( placement == LastVertex )
1014  {
1015  i = points.count() - 1;
1016  maxCount = points.count();
1017  }
1018  else
1019  {
1020  i = 0;
1021  maxCount = points.count();
1022  if ( points.first() == points.last() )
1023  isRing = true;
1024  }
1025 
1026  if ( offsetAlongLine > 0 && ( placement == FirstVertex || placement == LastVertex ) )
1027  {
1028  double distance;
1029  distance = placement == FirstVertex ? offsetAlongLine : -offsetAlongLine;
1030  renderOffsetVertexAlongLine( points, i, distance, context );
1031  // restore original rotation
1032  mMarker->setAngle( origAngle );
1033  return;
1034  }
1035 
1036  for ( ; i < maxCount; ++i )
1037  {
1038  if ( isRing && placement == Vertex && i == points.count() - 1 )
1039  {
1040  continue; // don't draw the last marker - it has been drawn already
1041  }
1042  // rotate marker (if desired)
1043  if ( mRotateMarker )
1044  {
1045  double angle = markerAngle( points, isRing, i );
1046  mMarker->setAngle( origAngle + angle * 180 / M_PI );
1047  }
1048 
1049  mMarker->renderPoint( points.at( i ), context.feature(), rc, -1, context.selected() );
1050  }
1051 
1052  // restore original rotation
1053  mMarker->setAngle( origAngle );
1054 }
1055 
1056 double QgsMarkerLineSymbolLayerV2::markerAngle( const QPolygonF& points, bool isRing, int vertex )
1057 {
1058  double angle = 0;
1059  const QPointF& pt = points[vertex];
1060 
1061  if ( isRing || ( vertex > 0 && vertex < points.count() - 1 ) )
1062  {
1063  int prevIndex = vertex - 1;
1064  int nextIndex = vertex + 1;
1065 
1066  if ( isRing && ( vertex == 0 || vertex == points.count() - 1 ) )
1067  {
1068  prevIndex = points.count() - 2;
1069  nextIndex = 1;
1070  }
1071 
1072  QPointF prevPoint, nextPoint;
1073  while ( prevIndex >= 0 )
1074  {
1075  prevPoint = points[ prevIndex ];
1076  if ( prevPoint != pt )
1077  {
1078  break;
1079  }
1080  --prevIndex;
1081  }
1082 
1083  while ( nextIndex < points.count() )
1084  {
1085  nextPoint = points[ nextIndex ];
1086  if ( nextPoint != pt )
1087  {
1088  break;
1089  }
1090  ++nextIndex;
1091  }
1092 
1093  if ( prevIndex >= 0 && nextIndex < points.count() )
1094  {
1095  angle = _averageAngle( prevPoint, pt, nextPoint );
1096  }
1097  }
1098  else //no ring and vertex is at start / at end
1099  {
1100  if ( vertex == 0 )
1101  {
1102  while ( vertex < points.size() - 1 )
1103  {
1104  const QPointF& nextPt = points[vertex+1];
1105  if ( pt != nextPt )
1106  {
1107  angle = MyLine( pt, nextPt ).angle();
1108  return angle;
1109  }
1110  ++vertex;
1111  }
1112  }
1113  else
1114  {
1115  // use last segment's angle
1116  while ( vertex >= 1 ) //in case of duplicated vertices, take the next suitable one
1117  {
1118  const QPointF& prevPt = points[vertex-1];
1119  if ( pt != prevPt )
1120  {
1121  angle = MyLine( prevPt, pt ).angle();
1122  return angle;
1123  }
1124  --vertex;
1125  }
1126  }
1127  }
1128  return angle;
1129 }
1130 
1131 void QgsMarkerLineSymbolLayerV2::renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolV2RenderContext& context )
1132 {
1133  if ( points.isEmpty() )
1134  return;
1135 
1136  QgsRenderContext& rc = context.renderContext();
1137  double origAngle = mMarker->angle();
1138  if ( distance == 0 )
1139  {
1140  // rotate marker (if desired)
1141  if ( mRotateMarker )
1142  {
1143  bool isRing = false;
1144  if ( points.first() == points.last() )
1145  isRing = true;
1146  double angle = markerAngle( points, isRing, vertex );
1147  mMarker->setAngle( origAngle + angle * 180 / M_PI );
1148  }
1149  mMarker->renderPoint( points[vertex], context.feature(), rc, -1, context.selected() );
1150  return;
1151  }
1152 
1153  int pointIncrement = distance > 0 ? 1 : -1;
1154  QPointF previousPoint = points[vertex];
1155  int startPoint = distance > 0 ? qMin( vertex + 1, points.count() - 1 ) : qMax( vertex - 1, 0 );
1156  int endPoint = distance > 0 ? points.count() - 1 : 0;
1157  double distanceLeft = qAbs( distance );
1158 
1159  for ( int i = startPoint; pointIncrement > 0 ? i <= endPoint : i >= endPoint; i += pointIncrement )
1160  {
1161  const QPointF& pt = points[i];
1162 
1163  if ( previousPoint == pt ) // must not be equal!
1164  continue;
1165 
1166  // create line segment
1167  MyLine l( previousPoint, pt );
1168 
1169  if ( distanceLeft < l.length() )
1170  {
1171  //destination point is in current segment
1172  QPointF markerPoint = previousPoint + l.diffForInterval( distanceLeft );
1173  // rotate marker (if desired)
1174  if ( mRotateMarker )
1175  {
1176  mMarker->setAngle( origAngle + ( l.angle() * 180 / M_PI ) );
1177  }
1178  mMarker->renderPoint( markerPoint, context.feature(), rc, -1, context.selected() );
1179  return;
1180  }
1181 
1182  distanceLeft -= l.length();
1183  previousPoint = pt;
1184  }
1185 
1186  //didn't find point
1187  return;
1188 }
1189 
1191 {
1192  if ( points.size() > 0 )
1193  {
1194  // calc length
1195  qreal length = 0;
1196  QPolygonF::const_iterator it = points.constBegin();
1197  QPointF last = *it;
1198  for ( ++it; it != points.constEnd(); ++it )
1199  {
1200  length += sqrt(( last.x() - it->x() ) * ( last.x() - it->x() ) +
1201  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
1202  last = *it;
1203  }
1204 
1205  // find the segment where the central point lies
1206  it = points.constBegin();
1207  last = *it;
1208  qreal last_at = 0, next_at = 0;
1209  QPointF next;
1210  int segment = 0;
1211  for ( ++it; it != points.constEnd(); ++it )
1212  {
1213  next = *it;
1214  next_at += sqrt(( last.x() - it->x() ) * ( last.x() - it->x() ) +
1215  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
1216  if ( next_at >= length / 2 )
1217  break; // we have reached the center
1218  last = *it;
1219  last_at = next_at;
1220  segment++;
1221  }
1222 
1223  // find out the central point on segment
1224  MyLine l( last, next ); // for line angle
1225  qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
1226  QPointF pt = last + ( next - last ) * k;
1227 
1228  // draw the marker
1229  double origAngle = mMarker->angle();
1230  if ( mRotateMarker )
1231  mMarker->setAngle( origAngle + l.angle() * 180 / M_PI );
1232  mMarker->renderPoint( pt, context.feature(), context.renderContext(), -1, context.selected() );
1233  if ( mRotateMarker )
1234  mMarker->setAngle( origAngle );
1235  }
1236 }
1237 
1238 
1240 {
1241  QgsStringMap map;
1242  map["rotate"] = ( mRotateMarker ? "1" : "0" );
1243  map["interval"] = QString::number( mInterval );
1244  map["offset"] = QString::number( mOffset );
1245  map["offset_along_line"] = QString::number( mOffsetAlongLine );
1246  map["offset_along_line_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mOffsetAlongLineUnit );
1247  map["offset_along_line_map_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( mOffsetAlongLineMapUnitScale );
1248  map["offset_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mOffsetUnit );
1249  map["offset_map_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( mOffsetMapUnitScale );
1250  map["interval_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( mIntervalUnit );
1251  map["interval_map_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( mIntervalMapUnitScale );
1252  if ( mPlacement == Vertex )
1253  map["placement"] = "vertex";
1254  else if ( mPlacement == LastVertex )
1255  map["placement"] = "lastvertex";
1256  else if ( mPlacement == FirstVertex )
1257  map["placement"] = "firstvertex";
1258  else if ( mPlacement == CentralPoint )
1259  map["placement"] = "centralpoint";
1260  else
1261  map["placement"] = "interval";
1262 
1264  return map;
1265 }
1266 
1268 {
1269  return mMarker;
1270 }
1271 
1273 {
1274  if ( symbol == NULL || symbol->type() != QgsSymbolV2::Marker )
1275  {
1276  delete symbol;
1277  return false;
1278  }
1279 
1280  delete mMarker;
1281  mMarker = static_cast<QgsMarkerSymbolV2*>( symbol );
1282  mColor = mMarker->color();
1283  return true;
1284 }
1285 
1287 {
1289  x->setSubSymbol( mMarker->clone() );
1290  x->setOffset( mOffset );
1291  x->setPlacement( mPlacement );
1292  x->setOffsetUnit( mOffsetUnit );
1300  return x;
1301 }
1302 
1303 void QgsMarkerLineSymbolLayerV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const
1304 {
1305  for ( int i = 0; i < mMarker->symbolLayerCount(); i++ )
1306  {
1307  QDomElement symbolizerElem = doc.createElement( "se:LineSymbolizer" );
1308  if ( !props.value( "uom", "" ).isEmpty() )
1309  symbolizerElem.setAttribute( "uom", props.value( "uom", "" ) );
1310  element.appendChild( symbolizerElem );
1311 
1312  // <Geometry>
1313  QgsSymbolLayerV2Utils::createGeometryElement( doc, symbolizerElem, props.value( "geom", "" ) );
1314 
1315  QString gap;
1316  switch ( mPlacement )
1317  {
1318  case FirstVertex:
1319  symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "placement", "firstPoint" ) );
1320  break;
1321  case LastVertex:
1322  symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "placement", "lastPoint" ) );
1323  break;
1324  case CentralPoint:
1325  symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "placement", "centralPoint" ) );
1326  break;
1327  case Vertex:
1328  // no way to get line/polygon's vertices, use a VendorOption
1329  symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "placement", "points" ) );
1330  break;
1331  default:
1332  gap = QString::number( mInterval );
1333  break;
1334  }
1335 
1336  if ( !mRotateMarker )
1337  {
1338  // markers in LineSymbolizer must be drawn following the line orientation,
1339  // use a VendorOption when no marker rotation
1340  symbolizerElem.appendChild( QgsSymbolLayerV2Utils::createVendorOptionElement( doc, "rotateMarker", "0" ) );
1341  }
1342 
1343  // <Stroke>
1344  QDomElement strokeElem = doc.createElement( "se:Stroke" );
1345  symbolizerElem.appendChild( strokeElem );
1346 
1347  // <GraphicStroke>
1348  QDomElement graphicStrokeElem = doc.createElement( "se:GraphicStroke" );
1349  strokeElem.appendChild( graphicStrokeElem );
1350 
1351  QgsSymbolLayerV2 *layer = mMarker->symbolLayer( i );
1352  QgsMarkerSymbolLayerV2 *markerLayer = static_cast<QgsMarkerSymbolLayerV2 *>( layer );
1353  if ( !markerLayer )
1354  {
1355  graphicStrokeElem.appendChild( doc.createComment( QString( "MarkerSymbolLayerV2 expected, %1 found. Skip it." ).arg( layer->layerType() ) ) );
1356  }
1357  else
1358  {
1359  markerLayer->writeSldMarker( doc, graphicStrokeElem, props );
1360  }
1361 
1362  if ( !gap.isEmpty() )
1363  {
1364  QDomElement gapElem = doc.createElement( "se:Gap" );
1365  QgsSymbolLayerV2Utils::createFunctionElement( doc, gapElem, gap );
1366  graphicStrokeElem.appendChild( gapElem );
1367  }
1368 
1369  if ( !qgsDoubleNear( mOffset, 0.0 ) )
1370  {
1371  QDomElement perpOffsetElem = doc.createElement( "se:PerpendicularOffset" );
1372  perpOffsetElem.appendChild( doc.createTextNode( QString::number( mOffset ) ) );
1373  symbolizerElem.appendChild( perpOffsetElem );
1374  }
1375  }
1376 }
1377 
1379 {
1380  QgsDebugMsg( "Entered." );
1381 
1382  QDomElement strokeElem = element.firstChildElement( "Stroke" );
1383  if ( strokeElem.isNull() )
1384  return NULL;
1385 
1386  QDomElement graphicStrokeElem = strokeElem.firstChildElement( "GraphicStroke" );
1387  if ( graphicStrokeElem.isNull() )
1388  return NULL;
1389 
1390  // retrieve vendor options
1391  bool rotateMarker = true;
1393 
1394  QgsStringMap vendorOptions = QgsSymbolLayerV2Utils::getVendorOptionList( element );
1395  for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
1396  {
1397  if ( it.key() == "placement" )
1398  {
1399  if ( it.value() == "points" ) placement = Vertex;
1400  else if ( it.value() == "firstPoint" ) placement = FirstVertex;
1401  else if ( it.value() == "lastPoint" ) placement = LastVertex;
1402  else if ( it.value() == "centralPoint" ) placement = CentralPoint;
1403  }
1404  else if ( it.value() == "rotateMarker" )
1405  {
1406  rotateMarker = it.value() == "0";
1407  }
1408  }
1409 
1410  QgsMarkerSymbolV2 *marker = 0;
1411 
1413  if ( l )
1414  {
1415  QgsSymbolLayerV2List layers;
1416  layers.append( l );
1417  marker = new QgsMarkerSymbolV2( layers );
1418  }
1419 
1420  if ( !marker )
1421  return NULL;
1422 
1423  double interval = 0.0;
1424  QDomElement gapElem = graphicStrokeElem.firstChildElement( "Gap" );
1425  if ( !gapElem.isNull() )
1426  {
1427  bool ok;
1428  double d = gapElem.firstChild().nodeValue().toDouble( &ok );
1429  if ( ok )
1430  interval = d;
1431  }
1432 
1433  double offset = 0.0;
1434  QDomElement perpOffsetElem = graphicStrokeElem.firstChildElement( "PerpendicularOffset" );
1435  if ( !perpOffsetElem.isNull() )
1436  {
1437  bool ok;
1438  double d = perpOffsetElem.firstChild().nodeValue().toDouble( &ok );
1439  if ( ok )
1440  offset = d;
1441  }
1442 
1443  QgsMarkerLineSymbolLayerV2* x = new QgsMarkerLineSymbolLayerV2( rotateMarker );
1444  x->setPlacement( placement );
1445  x->setInterval( interval );
1446  x->setSubSymbol( marker );
1447  x->setOffset( offset );
1448  return x;
1449 }
1450 
1452 {
1453  mMarker->setSize( width );
1454 }
1455 
1457 {
1458  return mMarker->size();
1459 }
1460 
1462 {
1464  mIntervalUnit = unit;
1465  mOffsetUnit = unit;
1466  mOffsetAlongLineUnit = unit;
1467 }
1468 
1470 {
1472  if ( mIntervalUnit != unit || mOffsetUnit != unit || mOffsetAlongLineUnit != unit )
1473  {
1474  return QgsSymbolV2::Mixed;
1475  }
1476  return unit;
1477 }
1478 
1480 {
1482  mIntervalMapUnitScale = scale;
1483  mOffsetMapUnitScale = scale;
1485 }
1486 
1488 {
1492  {
1493  return mOffsetMapUnitScale;
1494  }
1495  return QgsMapUnitScale();
1496 }
1497 
1499 {
1500  return ( mMarker->size() / 2.0 ) + mOffset;
1501 }
1502 
1503 
1504