Quantum GIS API Documentation  1.7.5-Wroclaw
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
qgslinesymbollayerv2.cpp
Go to the documentation of this file.
1 
2 #include "qgslinesymbollayerv2.h"
4 
5 #include "qgsrendercontext.h"
6 
7 #include <QPainter>
8 
9 #include <cmath>
10 
11 QgsSimpleLineSymbolLayerV2::QgsSimpleLineSymbolLayerV2( QColor color, double width, Qt::PenStyle penStyle )
12  : mPenStyle( penStyle ), mPenJoinStyle( DEFAULT_SIMPLELINE_JOINSTYLE ), mPenCapStyle( DEFAULT_SIMPLELINE_CAPSTYLE ), mOffset( 0 ), mUseCustomDashPattern( false )
13 {
14  mColor = color;
15  mWidth = width;
16  mCustomDashVector << 5 << 2;
17 }
18 
19 
21 {
25 
26  if ( props.contains( "color" ) )
27  color = QgsSymbolLayerV2Utils::decodeColor( props["color"] );
28  if ( props.contains( "width" ) )
29  width = props["width"].toDouble();
30  if ( props.contains( "penstyle" ) )
31  penStyle = QgsSymbolLayerV2Utils::decodePenStyle( props["penstyle"] );
32 
33 
34  QgsSimpleLineSymbolLayerV2* l = new QgsSimpleLineSymbolLayerV2( color, width, penStyle );
35  if ( props.contains( "offset" ) )
36  l->setOffset( props["offset"].toDouble() );
37  if ( props.contains( "joinstyle" ) )
39  if ( props.contains( "capstyle" ) )
41 
42  if ( props.contains( "use_custom_dash" ) )
43  {
44  l->setUseCustomDashPattern( props["use_custom_dash"].toInt() );
45  }
46  if ( props.contains( "customdash" ) )
47  {
49  }
50  return l;
51 }
52 
53 
55 {
56  return "SimpleLine";
57 }
58 
60 {
61  QColor penColor = mColor;
62  penColor.setAlphaF( context.alpha() );
63  mPen.setColor( penColor );
64  double scaledWidth = context.outputLineWidth( mWidth );
65  mPen.setWidthF( scaledWidth );
66  if ( mUseCustomDashPattern && scaledWidth != 0 )
67  {
68  mPen.setStyle( Qt::CustomDashLine );
69 
70  //scale pattern vector
71  QVector<qreal> scaledVector;
72  QVector<qreal>::const_iterator it = mCustomDashVector.constBegin();
73  for ( ; it != mCustomDashVector.constEnd(); ++it )
74  {
75  //the dash is specified in terms of pen widths, therefore the division
76  scaledVector << context.outputLineWidth(( *it ) / scaledWidth );
77  }
78  mPen.setDashPattern( scaledVector );
79  }
80  else
81  {
82  mPen.setStyle( mPenStyle );
83  }
84  mPen.setJoinStyle( mPenJoinStyle );
85  mPen.setCapStyle( mPenCapStyle );
86 
87  mSelPen = mPen;
88  QColor selColor = context.selectionColor();
89  if ( ! selectionIsOpaque ) selColor.setAlphaF( context.alpha() );
90  mSelPen.setColor( selColor );
91 }
92 
94 {
95 }
96 
98 {
99  QPainter* p = context.renderContext().painter();
100  if ( !p )
101  {
102  return;
103  }
104 
106  {
107  double scaledWidth = context.outputLineWidth( mWidth );
108  mPen.setWidthF( scaledWidth );
109  mSelPen.setWidthF( scaledWidth );
110  }
111 
112  p->setPen( context.selected() ? mSelPen : mPen );
113  if ( mOffset == 0 )
114  {
115  p->drawPolyline( points );
116  }
117  else
118  {
119  double scaledOffset = context.outputLineWidth( mOffset );
120  p->drawPolyline( ::offsetLine( points, scaledOffset ) );
121  }
122 }
123 
125 {
126  QgsStringMap map;
127  map["color"] = QgsSymbolLayerV2Utils::encodeColor( mColor );
128  map["width"] = QString::number( mWidth );
132  map["offset"] = QString::number( mOffset );
133  map["use_custom_dash"] = ( mUseCustomDashPattern ? "1" : "0" );
135  return map;
136 }
137 
139 {
141  l->setOffset( mOffset );
146  return l;
147 }
148 
149 
151 
152 
153 class MyLine
154 {
155  public:
156  MyLine( QPointF p1, QPointF p2 ) : mVertical( false ), mIncreasing( false ), mT( 0.0 ), mLength( 0.0 )
157  {
158  if ( p1 == p2 )
159  return; // invalid
160 
161  // tangent and direction
162  if ( p1.x() == p2.x() )
163  {
164  // vertical line - tangent undefined
165  mVertical = true;
166  mIncreasing = ( p2.y() > p1.y() );
167  }
168  else
169  {
170  mVertical = false;
171  mT = float( p2.y() - p1.y() ) / ( p2.x() - p1.x() );
172  mIncreasing = ( p2.x() > p1.x() );
173  }
174 
175  // length
176  double x = ( p2.x() - p1.x() );
177  double y = ( p2.y() - p1.y() );
178  mLength = sqrt( x * x + y * y );
179  }
180 
181  // return angle in radians
182  double angle()
183  {
184  double a = ( mVertical ? M_PI / 2 : atan( mT ) );
185 
186  if ( !mIncreasing )
187  a += M_PI;
188  return a;
189  }
190 
191  // return difference for x,y when going along the line with specified interval
192  QPointF diffForInterval( double interval )
193  {
194  if ( mVertical )
195  return ( mIncreasing ? QPointF( 0, interval ) : QPointF( 0, -interval ) );
196 
197  double alpha = atan( mT );
198  double dx = cos( alpha ) * interval;
199  double dy = sin( alpha ) * interval;
200  return ( mIncreasing ? QPointF( dx, dy ) : QPointF( -dx, -dy ) );
201  }
202 
203  double length() { return mLength; }
204 
205  protected:
206  bool mVertical;
208  double mT;
209  double mLength;
210 };
211 
212 
213 QgsMarkerLineSymbolLayerV2::QgsMarkerLineSymbolLayerV2( bool rotateMarker, double interval )
214 {
217  mMarker = NULL;
218  mOffset = 0;
220 
222 }
223 
225 {
226  delete mMarker;
227 }
228 
230 {
231  bool rotate = DEFAULT_MARKERLINE_ROTATE;
233 
234  if ( props.contains( "interval" ) )
235  interval = props["interval"].toDouble();
236  if ( props.contains( "rotate" ) )
237  rotate = ( props["rotate"] == "1" );
238 
239  QgsMarkerLineSymbolLayerV2* x = new QgsMarkerLineSymbolLayerV2( rotate, interval );
240  if ( props.contains( "offset" ) )
241  {
242  x->setOffset( props["offset"].toDouble() );
243  }
244 
245  if ( props.contains( "placement" ) )
246  {
247  if ( props["placement"] == "vertex" )
248  x->setPlacement( Vertex );
249  else if ( props["placement"] == "lastvertex" )
250  x->setPlacement( LastVertex );
251  else if ( props["placement"] == "firstvertex" )
253  else if ( props["placement"] == "centralpoint" )
255  else
256  x->setPlacement( Interval );
257  }
258  return x;
259 }
260 
262 {
263  return "MarkerLine";
264 }
265 
266 void QgsMarkerLineSymbolLayerV2::setColor( const QColor& color )
267 {
268  mMarker->setColor( color );
269  mColor = color;
270 }
271 
273 {
274  mMarker->setAlpha( context.alpha() );
275  mMarker->setOutputUnit( context.outputUnit() );
276 
277  // if being rotated, it gets initialized with every line segment
278  int hints = 0;
279  if ( mRotateMarker )
283  mMarker->setRenderHints( hints );
284 
285  mMarker->startRender( context.renderContext() );
286 }
287 
289 {
290  mMarker->stopRender( context.renderContext() );
291 }
292 
294 {
295  if ( mOffset == 0 )
296  {
297  if ( mPlacement == Interval )
298  renderPolylineInterval( points, context );
299  else if ( mPlacement == CentralPoint )
300  renderPolylineCentral( points, context );
301  else
302  renderPolylineVertex( points, context );
303  }
304  else
305  {
306  QPolygonF points2 = ::offsetLine( points, context.outputLineWidth( mOffset ) );
307  if ( mPlacement == Interval )
308  renderPolylineInterval( points2, context );
309  else if ( mPlacement == CentralPoint )
310  renderPolylineCentral( points2, context );
311  else
312  renderPolylineVertex( points2, context );
313  }
314 }
315 
317 {
318  if ( points.isEmpty() )
319  return;
320 
321  QPointF lastPt = points[0];
322  double lengthLeft = 0; // how much is left until next marker
323  bool first = true;
324  double origAngle = mMarker->angle();
325 
326  double painterUnitInterval = context.outputLineWidth( mInterval > 0 ? mInterval : 0.1 );
327 
328  QgsRenderContext& rc = context.renderContext();
329 
330  for ( int i = 1; i < points.count(); ++i )
331  {
332  const QPointF& pt = points[i];
333 
334  if ( lastPt == pt ) // must not be equal!
335  continue;
336 
337  // for each line, find out dx and dy, and length
338  MyLine l( lastPt, pt );
339  QPointF diff = l.diffForInterval( painterUnitInterval );
340 
341  // if there's some length left from previous line
342  // use only the rest for the first point in new line segment
343  double c = 1 - lengthLeft / painterUnitInterval;
344 
345  lengthLeft += l.length();
346 
347  // rotate marker (if desired)
348  if ( mRotateMarker )
349  {
350  mMarker->setAngle( origAngle + ( l.angle() * 180 / M_PI ) );
351  }
352 
353  // draw first marker
354  if ( first )
355  {
356  mMarker->renderPoint( lastPt, rc, -1, context.selected() );
357  first = false;
358  }
359 
360  // while we're not at the end of line segment, draw!
361  while ( lengthLeft > painterUnitInterval )
362  {
363  // "c" is 1 for regular point or in interval (0,1] for begin of line segment
364  lastPt += c * diff;
365  lengthLeft -= painterUnitInterval;
366  mMarker->renderPoint( lastPt, rc, -1, context.selected() );
367  c = 1; // reset c (if wasn't 1 already)
368  }
369 
370  lastPt = pt;
371  }
372 
373  // restore original rotation
374  mMarker->setAngle( origAngle );
375 
376 }
377 
378 static double _averageAngle( const QPointF& prevPt, const QPointF& pt, const QPointF& nextPt )
379 {
380  // calc average angle between the previous and next point
381  double a1 = MyLine( prevPt, pt ).angle();
382  double a2 = MyLine( pt, nextPt ).angle();
383  double unitX = cos( a1 ) + cos( a2 ), unitY = sin( a1 ) + sin( a2 );
384 
385  return atan2( unitY, unitX );
386 }
387 
389 {
390  if ( points.isEmpty() )
391  return;
392 
393  QgsRenderContext& rc = context.renderContext();
394 
395  double origAngle = mMarker->angle();
396  double angle;
397  int i, maxCount;
398  bool isRing = false;
399 
400  if ( mPlacement == FirstVertex )
401  {
402  i = 0;
403  maxCount = 1;
404  }
405  else if ( mPlacement == LastVertex )
406  {
407  i = points.count() - 1;
408  maxCount = points.count();
409  }
410  else
411  {
412  i = 0;
413  maxCount = points.count();
414  if ( points.first() == points.last() )
415  isRing = true;
416  }
417 
418  for ( ; i < maxCount; ++i )
419  {
420  const QPointF& pt = points[i];
421 
422  // rotate marker (if desired)
423  if ( mRotateMarker )
424  {
425  if ( i == 0 )
426  {
427  if ( !isRing )
428  {
429  // use first segment's angle
430  const QPointF& nextPt = points[i+1];
431  if ( pt == nextPt )
432  continue;
433  angle = MyLine( pt, nextPt ).angle();
434  }
435  else
436  {
437  // closed ring: use average angle between first and last segment
438  const QPointF& prevPt = points[points.count() - 2];
439  const QPointF& nextPt = points[1];
440  if ( prevPt == pt || nextPt == pt )
441  continue;
442 
443  angle = _averageAngle( prevPt, pt, nextPt );
444  }
445  }
446  else if ( i == points.count() - 1 )
447  {
448  if ( !isRing )
449  {
450  // use last segment's angle
451  const QPointF& prevPt = points[i-1];
452  if ( pt == prevPt )
453  continue;
454  angle = MyLine( prevPt, pt ).angle();
455  }
456  else
457  {
458  // don't draw the last marker - it has been drawn already
459  continue;
460  }
461  }
462  else
463  {
464  // use average angle
465  const QPointF& prevPt = points[i-1];
466  const QPointF& nextPt = points[i+1];
467  if ( prevPt == pt || nextPt == pt )
468  continue;
469 
470  angle = _averageAngle( prevPt, pt, nextPt );
471  }
472  mMarker->setAngle( origAngle + angle * 180 / M_PI );
473  }
474 
475  mMarker->renderPoint( points.at( i ), rc, -1, context.selected() );
476  }
477 
478  // restore original rotation
479  mMarker->setAngle( origAngle );
480 }
481 
483 {
484  // calc length
485  qreal length = 0;
486  QPolygonF::const_iterator it = points.constBegin();
487  QPointF last = *it;
488  for ( ++it; it != points.constEnd(); ++it )
489  {
490  length += sqrt(( last.x() - it->x() ) * ( last.x() - it->x() ) +
491  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
492  last = *it;
493  }
494 
495  // find the segment where the central point lies
496  it = points.constBegin();
497  last = *it;
498  qreal last_at = 0, next_at = 0;
499  QPointF next;
500  int segment = 0;
501  for ( ++it; it != points.constEnd(); ++it )
502  {
503  next = *it;
504  next_at += sqrt(( last.x() - it->x() ) * ( last.x() - it->x() ) +
505  ( last.y() - it->y() ) * ( last.y() - it->y() ) );
506  if ( next_at >= length / 2 )
507  break; // we have reached the center
508  last = *it;
509  last_at = next_at;
510  segment++;
511  }
512 
513  // find out the central point on segment
514  MyLine l( last, next ); // for line angle
515  qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
516  QPointF pt = last + ( next - last ) * k;
517 
518  // draw the marker
519  double origAngle = mMarker->angle();
520  if ( mRotateMarker )
521  mMarker->setAngle( origAngle + l.angle() * 180 / M_PI );
522  mMarker->renderPoint( pt, context.renderContext(), -1, context.selected() );
523  if ( mRotateMarker )
524  mMarker->setAngle( origAngle );
525 }
526 
527 
529 {
530  QgsStringMap map;
531  map["rotate"] = ( mRotateMarker ? "1" : "0" );
532  map["interval"] = QString::number( mInterval );
533  map["offset"] = QString::number( mOffset );
534  if ( mPlacement == Vertex )
535  map["placement"] = "vertex";
536  else if ( mPlacement == LastVertex )
537  map["placement"] = "lastvertex";
538  else if ( mPlacement == FirstVertex )
539  map["placement"] = "firstvertex";
540  else if ( mPlacement == CentralPoint )
541  map["placement"] = "centralpoint";
542  else
543  map["placement"] = "interval";
544  return map;
545 }
546 
548 {
549  return mMarker;
550 }
551 
553 {
554  if ( symbol == NULL || symbol->type() != QgsSymbolV2::Marker )
555  {
556  delete symbol;
557  return false;
558  }
559 
560  delete mMarker;
561  mMarker = static_cast<QgsMarkerSymbolV2*>( symbol );
562  mColor = mMarker->color();
563  return true;
564 }
565 
567 {
569  x->setSubSymbol( mMarker->clone() );
570  x->setOffset( mOffset );
571  x->setPlacement( mPlacement );
572  return x;
573 }
574 
576 {
577  mMarker->setSize( width );
578 }
579 
581 {
582  return mMarker->size();
583 }
584 
586 
588 {
589  mColor = color;
590  mWidth = width;
591 }
592 
594 {
595 }
596 
598 {
601 
602  if ( props.contains( "color" ) )
603  color = QgsSymbolLayerV2Utils::decodeColor( props["color"] );
604  if ( props.contains( "width" ) )
605  width = props["width"].toDouble();
606 
607  return new QgsLineDecorationSymbolLayerV2( color, width );
608 }
609 
611 {
612  return "LineDecoration";
613 }
614 
616 {
617  QColor penColor = mColor;
618  penColor.setAlphaF( context.alpha() );
619  mPen.setWidth( context.outputLineWidth( mWidth ) );
620  mPen.setColor( penColor );
621  QColor selColor = context.selectionColor();
622  if ( ! selectionIsOpaque ) selColor.setAlphaF( context.alpha() );
623  mSelPen.setWidth( context.outputLineWidth( mWidth ) );
624  mSelPen.setColor( selColor );
625 }
626 
628 {
629 }
630 
632 {
633  // draw arrow at the end of line
634 
635  QPainter* p = context.renderContext().painter();
636  if ( !p )
637  {
638  return;
639  }
640 
641  int cnt = points.count();
642  if ( cnt < 2 )
643  {
644  return;
645  }
646  QPointF p2 = points.at( --cnt );
647  QPointF p1 = points.at( --cnt );
648  while ( p2 == p1 && cnt )
649  p1 = points.at( --cnt );
650  if ( p1 == p2 ) {
651  // this is a collapsed line... don't bother drawing an arrow
652  // with arbitrary orientation
653  return;
654  }
655 
656  double angle = atan2( p2.y() - p1.y(), p2.x() - p1.x() );
657  double size = context.outputLineWidth( mWidth * 8 );
658  double angle1 = angle + M_PI / 6;
659  double angle2 = angle - M_PI / 6;
660 
661  QPointF p2_1 = p2 - QPointF( size * cos( angle1 ), size * sin( angle1 ) );
662  QPointF p2_2 = p2 - QPointF( size * cos( angle2 ), size * sin( angle2 ) );
663 
664  p->setPen( context.selected() ? mSelPen : mPen );
665  p->drawLine( p2, p2_1 );
666  p->drawLine( p2, p2_2 );
667 }
668 
670 {
671  QgsStringMap map;
672  map["color"] = QgsSymbolLayerV2Utils::encodeColor( mColor );
673  map["width"] = QString::number( mWidth );
674  return map;
675 }
676 
678 {
680 }