File indexing completed on 2024-12-08 06:35:36

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2011-2012 Florian Eßer <f.esser@rwth-aachen.de>
0004 // SPDX-FileCopyrightText: 2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0005 // SPDX-FileCopyrightText: 2013 Roman Karlstetter <roman.karlstetter@googlemail.com>
0006 //
0007 
0008 #include "ElevationProfileFloatItem.h"
0009 
0010 #include "ElevationProfileContextMenu.h"
0011 #include "ui_ElevationProfileConfigWidget.h"
0012 
0013 #include "MarbleModel.h"
0014 #include "MarbleWidget.h"
0015 #include "GeoDataPlacemark.h"
0016 #include "GeoDataTreeModel.h"
0017 #include "ViewportParams.h"
0018 #include "MarbleColors.h"
0019 #include "MarbleDirs.h"
0020 #include "ElevationModel.h"
0021 #include "MarbleGraphicsGridLayout.h"
0022 #include "MarbleDebug.h"
0023 #include "routing/RoutingManager.h"
0024 #include "routing/RoutingModel.h"
0025 
0026 #include <QContextMenuEvent>
0027 #include <QRect>
0028 #include <QPainter>
0029 #include <QPainterPath>
0030 #include <QPushButton>
0031 #include <QMenu>
0032 #include <QMouseEvent>
0033 
0034 namespace Marble
0035 {
0036 
0037 ElevationProfileFloatItem::ElevationProfileFloatItem( const MarbleModel *marbleModel )
0038         : AbstractFloatItem( marbleModel, QPointF( 220, 10.5 ), QSizeF( 0.0, 50.0 ) ),
0039         m_activeDataSource(nullptr),
0040         m_routeDataSource( marbleModel ? marbleModel->routingManager()->routingModel() : nullptr, marbleModel ? marbleModel->elevationModel() : nullptr, this ),
0041         m_trackDataSource( marbleModel ? marbleModel->treeModel() : nullptr, this ),
0042         m_configDialog( nullptr ),
0043         ui_configWidget( nullptr ),
0044         m_leftGraphMargin( 0 ),
0045         m_eleGraphWidth( 0 ),
0046         m_viewportWidth( 0 ),
0047         m_shrinkFactorY( 1.2 ),
0048         m_fontHeight( 10 ),
0049         m_markerPlacemark( new GeoDataPlacemark ),
0050         m_documentIndex( -1 ),
0051         m_cursorPositionX( 0 ),
0052         m_isInitialized( false ),
0053         m_contextMenu( nullptr ),
0054         m_marbleWidget( nullptr ),
0055         m_firstVisiblePoint( 0 ),
0056         m_lastVisiblePoint( 0 ),
0057         m_zoomToViewport( false )
0058 {
0059     setVisible( false );
0060     bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
0061     if ( smallScreen ) {
0062         setPosition( QPointF( 10.5, 10.5 ) );
0063     }
0064     bool const highRes = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::HighResolution;
0065     m_eleGraphHeight = highRes ? 100 : 50; /// TODO make configurable
0066 
0067     setPadding( 1 );
0068 
0069     m_markerDocument.setDocumentRole( UnknownDocument );
0070     m_markerDocument.setName(QStringLiteral("Elevation Profile"));
0071 
0072     m_markerPlacemark->setName(QStringLiteral("Elevation Marker"));
0073     m_markerPlacemark->setVisible( false );
0074     m_markerDocument.append( m_markerPlacemark );
0075 
0076     m_contextMenu = new ElevationProfileContextMenu(this);
0077     connect( &m_trackDataSource, SIGNAL(sourceCountChanged()), m_contextMenu, SLOT(updateContextMenuEntries()) );
0078     connect( &m_routeDataSource, SIGNAL(sourceCountChanged()), m_contextMenu, SLOT(updateContextMenuEntries()) );
0079 }
0080 
0081 ElevationProfileFloatItem::~ElevationProfileFloatItem()
0082 {
0083 }
0084 
0085 QStringList ElevationProfileFloatItem::backendTypes() const
0086 {
0087     return QStringList(QStringLiteral("elevationprofile"));
0088 }
0089 
0090 qreal ElevationProfileFloatItem::zValue() const
0091 {
0092     return 3.0;
0093 }
0094 
0095 QString ElevationProfileFloatItem::name() const
0096 {
0097     return tr("Elevation Profile");
0098 }
0099 
0100 QString ElevationProfileFloatItem::guiString() const
0101 {
0102     return tr("&Elevation Profile");
0103 }
0104 
0105 QString ElevationProfileFloatItem::nameId() const
0106 {
0107     return QStringLiteral("elevationprofile");
0108 }
0109 
0110 QString ElevationProfileFloatItem::version() const
0111 {
0112     return QStringLiteral("1.2"); // TODO: increase to 1.3 ?
0113 }
0114 
0115 QString ElevationProfileFloatItem::description() const
0116 {
0117     return tr( "A float item that shows the elevation profile of the current route." );
0118 }
0119 
0120 QString ElevationProfileFloatItem::copyrightYears() const
0121 {
0122     return QStringLiteral("2011, 2012, 2013");
0123 }
0124 
0125 QVector<PluginAuthor> ElevationProfileFloatItem::pluginAuthors() const
0126 {
0127     return QVector<PluginAuthor>()
0128             << PluginAuthor(QStringLiteral("Florian Eßer"),QStringLiteral("f.esser@rwth-aachen.de"))
0129             << PluginAuthor(QStringLiteral("Bernhard Beschow"), QStringLiteral("bbeschow@cs.tu-berlin.de"))
0130             << PluginAuthor(QStringLiteral("Roman Karlstetter"), QStringLiteral("roman.karlstetter@googlemail.com"));
0131 }
0132 
0133 QIcon ElevationProfileFloatItem::icon () const
0134 {
0135     return QIcon(QStringLiteral(":/icons/elevationprofile.png"));
0136 }
0137 
0138 void ElevationProfileFloatItem::initialize ()
0139 {
0140     connect( marbleModel()->elevationModel(), SIGNAL(updateAvailable()), &m_routeDataSource, SLOT(requestUpdate()) );
0141     connect( marbleModel()->routingManager()->routingModel(), SIGNAL(currentRouteChanged()), &m_routeDataSource, SLOT(requestUpdate()) );
0142     connect( this, SIGNAL(dataUpdated()), SLOT(forceRepaint()) );
0143     switchDataSource(&m_routeDataSource);
0144 
0145     m_fontHeight = QFontMetricsF( font() ).ascent() + 1;
0146     m_leftGraphMargin = QFontMetricsF( font() ).width( "0000 m" ); /// TODO make this dynamic according to actual need
0147 
0148     m_isInitialized = true;
0149 }
0150 
0151 bool ElevationProfileFloatItem::isInitialized () const
0152 {
0153     return m_isInitialized;
0154 }
0155 
0156 void ElevationProfileFloatItem::setProjection( const ViewportParams *viewport )
0157 {
0158     if ( !( viewport->width() == m_viewportWidth && m_isInitialized ) ) {
0159         bool const highRes = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::HighResolution;
0160         int const widthRatio = highRes ? 2 : 3;
0161         setContentSize( QSizeF( viewport->width() / widthRatio,
0162                                 m_eleGraphHeight + m_fontHeight * 2.5 ) );
0163         m_eleGraphWidth = contentSize().width() - m_leftGraphMargin;
0164         m_axisX.setLength( m_eleGraphWidth );
0165         m_axisY.setLength( m_eleGraphHeight );
0166         m_axisX.setTickCount( 3, m_eleGraphWidth / ( m_leftGraphMargin * 1.5 ) );
0167         m_axisY.setTickCount( 2, m_eleGraphHeight / m_fontHeight );
0168         m_viewportWidth = viewport->width();
0169         bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
0170         if ( !m_isInitialized && !smallScreen ) {
0171             setPosition( QPointF( (viewport->width() - contentSize().width()) / 2 , 10.5 ) );
0172         }
0173     }
0174 
0175     update();
0176 
0177     AbstractFloatItem::setProjection( viewport );
0178 }
0179 
0180 void ElevationProfileFloatItem::paintContent( QPainter *painter )
0181 {
0182     // do not try to draw if not initialized
0183     if(!isInitialized()) {
0184         return;
0185     }
0186     painter->save();
0187     painter->setRenderHint( QPainter::Antialiasing, true );
0188     painter->setFont( font() );
0189 
0190     if ( ! ( m_activeDataSource->isDataAvailable() && m_eleData.size() > 0 ) ) {
0191         painter->setPen( QColor( Qt::black ) );
0192         QString text = tr( "Create a route or load a track from file to view its elevation profile." );
0193         painter->drawText( contentRect().toRect(), Qt::TextWordWrap | Qt::AlignCenter, text );
0194         painter->restore();
0195         return;
0196     }
0197     if ( m_zoomToViewport && ( m_lastVisiblePoint - m_firstVisiblePoint < 5 ) ) {
0198         painter->setPen( QColor( Qt::black ) );
0199         QString text = tr( "Not enough points in the current viewport.\nTry to disable 'Zoom to viewport'." );
0200         painter->drawText( contentRect().toRect(), Qt::TextWordWrap | Qt::AlignCenter, text );
0201         painter->restore();
0202         return;
0203     }
0204 
0205     QString intervalStr;
0206     int lastStringEnds;
0207 
0208     // draw viewport bounds
0209     if ( ! m_zoomToViewport && ( m_firstVisiblePoint > 0 || m_lastVisiblePoint < m_eleData.size() - 1 ) ) {
0210         QColor color( Qt::black );
0211         color.setAlpha( 64 );
0212         QRect rect;
0213         rect.setLeft( m_leftGraphMargin + m_eleData.value( m_firstVisiblePoint ).x() * m_eleGraphWidth / m_axisX.range() );
0214         rect.setTop( 0 );
0215         rect.setWidth( ( m_eleData.value( m_lastVisiblePoint ).x() - m_eleData.value( m_firstVisiblePoint ).x() ) * m_eleGraphWidth / m_axisX.range() );
0216         rect.setHeight( m_eleGraphHeight );
0217         painter->fillRect( rect, color );
0218     }
0219 
0220     // draw X and Y axis
0221     painter->setPen( Oxygen::aluminumGray4 );
0222     painter->drawLine( m_leftGraphMargin, m_eleGraphHeight, contentSize().width(), m_eleGraphHeight );
0223     painter->drawLine( m_leftGraphMargin, m_eleGraphHeight, m_leftGraphMargin, 0 );
0224 
0225     // draw Y grid and labels
0226     painter->setPen( QColor( Qt::black ) );
0227     QPen dashedPen( Qt::DashLine );
0228     dashedPen.setColor( Oxygen::aluminumGray4 );
0229     QRect labelRect( 0, 0, m_leftGraphMargin - 1, m_fontHeight + 2 );
0230     lastStringEnds = m_eleGraphHeight + m_fontHeight;
0231 //     painter->drawText(m_leftGraphMargin + 1, m_fontHeight, QLatin1Char('[') + m_axisY.unit() + QLatin1Char(']'));
0232     for ( const AxisTick &tick: m_axisY.ticks() ) {
0233         const int posY = m_eleGraphHeight - tick.position;
0234         painter->setPen( dashedPen );
0235         painter->drawLine( m_leftGraphMargin, posY, contentSize().width(), posY );
0236 
0237         labelRect.moveCenter( QPoint( labelRect.center().x(), posY ) );
0238         if ( labelRect.top() < 0 ) {
0239             // don't cut off uppermost label
0240             labelRect.moveTop( 0 );
0241         }
0242         if ( labelRect.bottom() >= lastStringEnds ) {
0243             // Don't print overlapping labels
0244             continue;
0245         }
0246         lastStringEnds = labelRect.top();
0247         painter->setPen( QColor( Qt::black ) );
0248         intervalStr.setNum( tick.value * m_axisY.scale() );
0249         painter->drawText( labelRect, Qt::AlignRight, intervalStr );
0250     }
0251 
0252     // draw X grid and labels
0253     painter->setPen( QColor( Qt::black ) );
0254     labelRect.moveTop( m_eleGraphHeight + 1 );
0255     lastStringEnds = 0;
0256     for ( const AxisTick &tick: m_axisX.ticks() ) {
0257         const int posX = m_leftGraphMargin + tick.position;
0258         painter->setPen( dashedPen );
0259         painter->drawLine( posX, 0, posX, m_eleGraphHeight );
0260 
0261         intervalStr.setNum( tick.value * m_axisX.scale() );
0262         if ( tick.position == m_axisX.ticks().last().position ) {
0263             intervalStr += QLatin1Char(' ') + m_axisX.unit();
0264         }
0265         labelRect.setWidth( QFontMetricsF( font() ).width( intervalStr ) * 1.5 );
0266         labelRect.moveCenter( QPoint( posX, labelRect.center().y() ) );
0267         if ( labelRect.right() > m_leftGraphMargin + m_eleGraphWidth ) {
0268             // don't cut off rightmost label
0269             labelRect.moveRight( m_leftGraphMargin + m_eleGraphWidth );
0270         }
0271         if ( labelRect.left() <= lastStringEnds ) {
0272             // Don't print overlapping labels
0273             continue;
0274         }
0275         lastStringEnds = labelRect.right();
0276         painter->setPen( QColor( Qt::black ) );
0277         painter->drawText( labelRect, Qt::AlignCenter, intervalStr );
0278     }
0279 
0280     // display elevation gain/loss data
0281     painter->setPen( QColor( Qt::black ) );
0282     intervalStr = tr( "Difference: %1 %2" )
0283                    .arg( QString::number( m_gain - m_loss, 'f', 0 ) )
0284                    .arg( m_axisY.unit() );
0285     intervalStr += QString::fromUtf8( "  (↗ %1 %3  ↘ %2 %3)" )
0286                    .arg( QString::number( m_gain, 'f', 0 ) )
0287                    .arg( QString::number( m_loss, 'f', 0 ) )
0288                    .arg( m_axisY.unit() );
0289     painter->drawText( contentRect().toRect(), Qt::AlignBottom | Qt::AlignCenter, intervalStr );
0290 
0291     // draw elevation profile
0292     painter->setPen( QColor( Qt::black ) );
0293     bool const highRes = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::HighResolution;
0294     QPen pen = painter->pen();
0295     pen.setWidth( highRes ? 2 : 1 );
0296     painter->setPen( pen );
0297 
0298     QLinearGradient fillGradient( 0, 0, 0, m_eleGraphHeight );
0299     QColor startColor = Oxygen::forestGreen4;
0300     QColor endColor = Oxygen::hotOrange4;
0301     startColor.setAlpha( 200 );
0302     endColor.setAlpha( 32 );
0303     fillGradient.setColorAt( 0.0, startColor );
0304     fillGradient.setColorAt( 1.0, endColor );
0305     QBrush brush = QBrush( fillGradient );
0306     painter->setBrush( brush );
0307 
0308     QPoint oldPos;
0309     oldPos.setX( m_leftGraphMargin );
0310     oldPos.setY( ( m_axisY.minValue() - m_axisY.minValue() )
0311                  * m_eleGraphHeight / ( m_axisY.range() / m_shrinkFactorY ) );
0312     oldPos.setY( m_eleGraphHeight - oldPos.y() );
0313     QPainterPath path;
0314     path.moveTo( oldPos.x(), m_eleGraphHeight );
0315     path.lineTo( oldPos.x(), oldPos.y() );
0316 
0317     const int start = m_zoomToViewport ? m_firstVisiblePoint : 0;
0318     const int end = m_zoomToViewport ? m_lastVisiblePoint : m_eleData.size() - 1;
0319     for ( int i = start; i <= end; ++i ) {
0320         QPoint newPos;
0321         if ( i == start ) {
0322             // make sure the plot always starts at the y-axis
0323             newPos.setX( 0 );
0324         } else {
0325             newPos.setX( ( m_eleData.value(i).x() - m_axisX.minValue() ) * m_eleGraphWidth / m_axisX.range() );
0326         }
0327         newPos.rx() += m_leftGraphMargin;
0328         if ( newPos.x() != oldPos.x() || newPos.y() != oldPos.y()  ) {
0329             newPos.setY( ( m_eleData.value(i).y() - m_axisY.minValue() )
0330                          * m_eleGraphHeight / ( m_axisY.range() * m_shrinkFactorY ) );
0331             newPos.setY( m_eleGraphHeight - newPos.y() );
0332             path.lineTo( newPos.x(), newPos.y() );
0333             oldPos = newPos;
0334         }
0335     }
0336     path.lineTo( oldPos.x(), m_eleGraphHeight );
0337     // fill
0338     painter->setPen( QPen( Qt::NoPen ) );
0339     painter->drawPath( path );
0340     // contour
0341     // "remove" the first and last path element first, they are only used to fill down to the bottom
0342     painter->setBrush( QBrush( Qt::NoBrush ) );
0343     path.setElementPositionAt( 0, path.elementAt( 1 ).x,  path.elementAt( 1 ).y );
0344     path.setElementPositionAt( path.elementCount()-1,
0345                                path.elementAt( path.elementCount()-2 ).x,
0346                                path.elementAt( path.elementCount()-2 ).y );
0347     painter->setPen( pen );
0348     painter->drawPath( path );
0349 
0350     pen.setWidth( 1 );
0351     painter->setPen( pen );
0352 
0353     // draw interactive cursor
0354     const GeoDataCoordinates currentPoint = m_markerPlacemark->coordinate();
0355     if ( currentPoint.isValid() ) {
0356         painter->setPen( QColor( Qt::white ) );
0357         painter->drawLine( m_leftGraphMargin + m_cursorPositionX, 0,
0358                            m_leftGraphMargin + m_cursorPositionX, m_eleGraphHeight );
0359         qreal xpos = m_axisX.minValue() + ( m_cursorPositionX / m_eleGraphWidth ) * m_axisX.range();
0360         qreal ypos = m_eleGraphHeight - ( ( currentPoint.altitude() - m_axisY.minValue() ) / ( qMax<qreal>( 1.0, m_axisY.range() ) * m_shrinkFactorY ) ) * m_eleGraphHeight;
0361 
0362         painter->drawLine( m_leftGraphMargin + m_cursorPositionX - 5, ypos,
0363                            m_leftGraphMargin + m_cursorPositionX + 5, ypos );
0364         intervalStr.setNum( xpos * m_axisX.scale(), 'f', 2 );
0365         intervalStr += QLatin1Char(' ') + m_axisX.unit();
0366         int currentStringBegin = m_leftGraphMargin + m_cursorPositionX
0367                              - QFontMetricsF( font() ).width( intervalStr ) / 2;
0368         painter->drawText( currentStringBegin, contentSize().height() - 1.5 * m_fontHeight, intervalStr );
0369 
0370         intervalStr.setNum( currentPoint.altitude(), 'f', 1 );
0371         intervalStr += QLatin1Char(' ') + m_axisY.unit();
0372         if ( m_cursorPositionX + QFontMetricsF( font() ).width( intervalStr ) + m_leftGraphMargin
0373                 < m_eleGraphWidth ) {
0374             currentStringBegin = ( m_leftGraphMargin + m_cursorPositionX + 5 + 2 );
0375         } else {
0376             currentStringBegin = m_leftGraphMargin + m_cursorPositionX - 5
0377                                  - QFontMetricsF( font() ).width( intervalStr ) * 1.5;
0378         }
0379         // Make sure the text still fits into the window
0380         while ( ypos < m_fontHeight ) {
0381             ypos++;
0382         }
0383         painter->drawText( currentStringBegin, ypos + m_fontHeight / 2, intervalStr );
0384     }
0385 
0386     painter->restore();
0387 }
0388 
0389 QDialog *ElevationProfileFloatItem::configDialog() //FIXME TODO Make a config dialog? /// TODO what is this comment?
0390 {
0391     if ( !m_configDialog ) {
0392         // Initializing configuration dialog
0393         m_configDialog = new QDialog();
0394         ui_configWidget = new Ui::ElevationProfileConfigWidget;
0395         ui_configWidget->setupUi( m_configDialog );
0396 
0397         readSettings();
0398 
0399         connect( ui_configWidget->m_buttonBox, SIGNAL(accepted()), SLOT(writeSettings()) );
0400         connect( ui_configWidget->m_buttonBox, SIGNAL(rejected()), SLOT(readSettings()) );
0401         QPushButton *applyButton = ui_configWidget->m_buttonBox->button( QDialogButtonBox::Apply );
0402         connect( applyButton, SIGNAL(clicked()), this, SLOT(writeSettings()) );
0403     }
0404     return m_configDialog;
0405 }
0406 
0407 void ElevationProfileFloatItem::contextMenuEvent( QWidget *w, QContextMenuEvent *e )
0408 {
0409     Q_ASSERT( m_contextMenu );
0410     m_contextMenu->getMenu()->exec( w->mapToGlobal( e->pos() ) );
0411 }
0412 
0413 bool ElevationProfileFloatItem::eventFilter( QObject *object, QEvent *e )
0414 {
0415     if ( !enabled() || !visible() ) {
0416         return false;
0417     }
0418 
0419     MarbleWidget *widget = dynamic_cast<MarbleWidget*>( object );
0420     if ( !widget ) {
0421         return AbstractFloatItem::eventFilter(object,e);
0422     }
0423 
0424     if ( widget && !m_marbleWidget ) {
0425         m_marbleWidget = widget;
0426         connect( this, SIGNAL(dataUpdated()), this, SLOT(updateVisiblePoints()) );
0427         connect( m_marbleWidget, SIGNAL(visibleLatLonAltBoxChanged(GeoDataLatLonAltBox)),
0428                  this, SLOT(updateVisiblePoints()) );
0429         connect( this, SIGNAL(settingsChanged(QString)), this, SLOT(updateVisiblePoints()) );
0430     }
0431 
0432     if ( e->type() == QEvent::MouseButtonDblClick || e->type() == QEvent::MouseMove ) {
0433         GeoDataTreeModel *const treeModel = const_cast<MarbleModel *>( marbleModel() )->treeModel();
0434 
0435         QMouseEvent *event = static_cast<QMouseEvent*>( e  );
0436         QRectF plotRect = QRectF ( m_leftGraphMargin, 0, m_eleGraphWidth, contentSize().height() );
0437         plotRect.translate( positivePosition() );
0438         plotRect.translate( padding(), padding() );
0439 
0440         // for antialiasing: increase size by 1 px to each side
0441         plotRect.translate(-1, -1);
0442         plotRect.setSize(plotRect.size() + QSize(2, 2) );
0443 
0444         const bool cursorAboveFloatItem = plotRect.contains(event->pos());
0445 
0446         if ( cursorAboveFloatItem ) {
0447             const int start = m_zoomToViewport ? m_firstVisiblePoint : 0;
0448             const int end = m_zoomToViewport ? m_lastVisiblePoint : m_eleData.size();
0449 
0450             // Double click triggers recentering the map at the specified position
0451             if ( e->type() == QEvent::MouseButtonDblClick ) {
0452                 const QPointF mousePosition = event->pos() - plotRect.topLeft();
0453                 const int xPos = mousePosition.x();
0454                 for ( int i = start; i < end; ++i) {
0455                     const int plotPos = ( m_eleData.value(i).x() - m_axisX.minValue() ) * m_eleGraphWidth / m_axisX.range();
0456                     if ( plotPos >= xPos ) {
0457                         widget->centerOn( m_points[i], true );
0458                         break;
0459                     }
0460                 }
0461                 return true;
0462             }
0463 
0464             if ( e->type() == QEvent::MouseMove && !(event->buttons() & Qt::LeftButton) ) {
0465                 // Cross hair cursor when moving above the float item
0466                 // and mark the position on the graph
0467                 widget->setCursor(QCursor(Qt::CrossCursor));
0468                 if ( m_cursorPositionX != event->pos().x() - plotRect.left() ) {
0469                     m_cursorPositionX = event->pos().x() - plotRect.left();
0470                     const qreal xpos = m_axisX.minValue() + ( m_cursorPositionX / m_eleGraphWidth ) * m_axisX.range();
0471                     GeoDataCoordinates currentPoint; // invalid coordinates
0472                     for ( int i = start; i < end; ++i) {
0473                         if ( m_eleData.value(i).x() >= xpos ) {
0474                             currentPoint = m_points[i];
0475                             currentPoint.setAltitude( m_eleData.value(i).y() );
0476                             break;
0477                         }
0478                     }
0479                     m_markerPlacemark->setCoordinate( currentPoint );
0480                     if ( m_documentIndex < 0 ) {
0481                         m_documentIndex = treeModel->addDocument( &m_markerDocument );
0482                     }
0483                     emit repaintNeeded();
0484                 }
0485 
0486                 return true;
0487             }
0488         }
0489         else {
0490             if ( m_documentIndex >= 0 ) {
0491                 m_markerPlacemark->setCoordinate( GeoDataCoordinates() ); // set to invalid
0492                 treeModel->removeDocument( &m_markerDocument );
0493                 m_documentIndex = -1;
0494                 emit repaintNeeded();
0495             }
0496         }
0497     }
0498 
0499     return AbstractFloatItem::eventFilter(object,e);
0500 }
0501 
0502 void ElevationProfileFloatItem::handleDataUpdate(const GeoDataLineString &points, const QVector<QPointF> &eleData)
0503 {
0504     m_eleData = eleData;
0505     m_points = points;
0506     calculateStatistics( m_eleData );
0507     if ( m_eleData.length() >= 2 ) {
0508         m_axisX.setRange( m_eleData.first().x(), m_eleData.last().x() );
0509         m_axisY.setRange( qMin( m_minElevation, qreal( 0.0 ) ), m_maxElevation );
0510     }
0511 
0512     emit dataUpdated();
0513 }
0514 
0515 void ElevationProfileFloatItem::updateVisiblePoints()
0516 {
0517     if ( ! m_activeDataSource->isDataAvailable() || m_points.size() < 2 ) {
0518         return;
0519     }
0520 
0521     // find the longest visible route section on screen
0522     QList<QList<int> > routeSegments;
0523     QList<int> currentRouteSegment;
0524     for ( int i = 0; i < m_eleData.count(); i++ ) {
0525         qreal lon = m_points[i].longitude(GeoDataCoordinates::Degree);
0526         qreal lat = m_points[i].latitude (GeoDataCoordinates::Degree);
0527         qreal x = 0;
0528         qreal y = 0;
0529 
0530         if ( m_marbleWidget->screenCoordinates(lon, lat, x, y) ) {
0531             // on screen --> add point to list
0532             currentRouteSegment.append(i);
0533         } else {
0534             // off screen --> start new list
0535             if ( !currentRouteSegment.isEmpty() ) {
0536                 routeSegments.append( currentRouteSegment );
0537                 currentRouteSegment.clear();
0538             }
0539         }
0540     }
0541     routeSegments.append( currentRouteSegment ); // in case the route ends on screen
0542 
0543     int maxLenght = 0;
0544     for ( const QList<int> &currentRouteSegment: routeSegments ) {
0545         if ( currentRouteSegment.size() > maxLenght ) {
0546             maxLenght = currentRouteSegment.size() ;
0547             m_firstVisiblePoint = currentRouteSegment.first();
0548             m_lastVisiblePoint  = currentRouteSegment.last();
0549         }
0550     }
0551     if ( m_firstVisiblePoint < 0 ) {
0552         m_firstVisiblePoint = 0;
0553     }
0554     if ( m_lastVisiblePoint < 0 || m_lastVisiblePoint >= m_eleData.count() ) {
0555         m_lastVisiblePoint = m_eleData.count() - 1;
0556     }
0557 
0558     // include setting range to statistics and test for m_zoomToViewport in calculateStatistics();
0559     if ( m_zoomToViewport ) {
0560         calculateStatistics( m_eleData );
0561         m_axisX.setRange( m_eleData.value( m_firstVisiblePoint ).x(),
0562                           m_eleData.value( m_lastVisiblePoint  ).x() );
0563         m_axisY.setRange( m_minElevation, m_maxElevation );
0564     }
0565 
0566     return;
0567 }
0568 
0569 void ElevationProfileFloatItem::calculateStatistics(const QVector<QPointF> &eleData)
0570 {
0571     // This basically calculates the important peaks of the moving average filtered elevation and
0572     // calculates the elevation data based on this points.
0573     // This is done by always placing the averaging window in a way that it starts or ends at an
0574     // original data point. This should ensure that all minima/maxima of the moving average
0575     // filtered data are covered.
0576     const qreal averageDistance = 200.0;
0577 
0578     m_maxElevation = 0.0;
0579     m_minElevation = invalidElevationData;
0580     m_gain = 0.0;
0581     m_loss = 0.0;
0582     const int start = m_zoomToViewport ? m_firstVisiblePoint : 0;
0583     const int end = m_zoomToViewport ? m_lastVisiblePoint + 1 : eleData.size();
0584 
0585     if( start < end ) {
0586         qreal lastX = eleData.value( start ).x();
0587         qreal lastY = eleData.value( start ).y();
0588         qreal nextX = eleData.value( start + 1 ).x();
0589         qreal nextY = eleData.value( start + 1 ).y();
0590 
0591         m_maxElevation = qMax( lastY, nextY );
0592         m_minElevation = qMin( lastY, nextY );
0593 
0594         int averageStart = start;
0595         if(lastX + averageDistance < eleData.value( start + 2 ).x())
0596             ++averageStart;
0597 
0598         for ( int index = start + 2; index <= end; ++index ) {
0599             qreal indexX = index < end ? eleData.value( index ).x() : eleData.value( end - 1 ).x() + averageDistance;
0600             qreal indexY = eleData.value( qMin( index, end - 1 ) ).y();
0601             m_maxElevation = qMax( m_maxElevation, indexY );
0602             m_minElevation = qMin( m_minElevation, indexY );
0603 
0604             // Low-pass filtering (moving average) of the elevation profile to calculate gain and loss values
0605             // not always the best method, see for example
0606             // http://www.ikg.uni-hannover.de/fileadmin/ikg/staff/thesis/finished/documents/StudArb_Schulze.pdf
0607             // (German), chapter 4.2
0608 
0609             // Average over the part ending with the previous point.
0610             // Do complete recalculation to avoid accumulation of floating point artifacts.
0611             nextY = 0;
0612             qreal averageX = nextX - averageDistance;
0613             for( int averageIndex = averageStart; averageIndex < index; ++averageIndex ) {
0614                 qreal nextAverageX = eleData.value( averageIndex ).x();
0615                 qreal ratio = ( nextAverageX - averageX ) / averageDistance; // Weighting of original data based on covered distance
0616                 nextY += eleData.value( qMax( averageIndex - 1, 0 ) ).y() * ratio;
0617                 averageX = nextAverageX;
0618             }
0619 
0620             while( averageStart < index ) {
0621                 // This handles the part ending with the previous point on the first iteration and the parts starting with averageStart afterwards
0622                 if ( nextY > lastY ) {
0623                     m_gain += nextY - lastY;
0624                 } else {
0625                     m_loss += lastY - nextY;
0626                 }
0627 
0628                 // Here we split the data into parts that average over the same data points
0629                 // As soon as the end of the averaging window reaches the current point we reached the end of the current part
0630                 lastX = nextX;
0631                 lastY = nextY;
0632                 nextX = eleData.value( averageStart ).x() + averageDistance;
0633                 if( nextX >= indexX ) {
0634                     break;
0635                 }
0636 
0637                 // We don't need to recalculate the average completely, just remove the reached point
0638                 qreal ratio = (nextX - lastX) / averageDistance;
0639                 nextY += ( eleData.value( index - 1 ).y() - eleData.value( qMax( averageStart - 1, 0 ) ).y() ) * ratio;
0640                 ++averageStart;
0641             }
0642 
0643             // This is for the next part already, the end of the averaging window is at the following point
0644             nextX = indexX;
0645         }
0646 
0647         // Also include the last point
0648         nextY = eleData.value( end - 1 ).y();
0649         if ( nextY > lastY ) {
0650             m_gain += nextY - lastY;
0651         } else {
0652             m_loss += lastY - nextY;
0653         }
0654     }
0655 }
0656 
0657 void ElevationProfileFloatItem::forceRepaint()
0658 {
0659     // We add one pixel as antialiasing could result into painting on these pixels to.
0660     QRectF floatItemRect = QRectF( positivePosition() - QPoint( 1, 1 ),
0661                                    size() + QSize( 2, 2 ) );
0662     update();
0663     emit repaintNeeded( floatItemRect.toRect() );
0664 }
0665 
0666 void ElevationProfileFloatItem::readSettings()
0667 {
0668     if ( !m_configDialog )
0669         return;
0670 
0671     if ( m_zoomToViewport ) {
0672         ui_configWidget->m_zoomToViewportCheckBox->setCheckState( Qt::Checked );
0673     }
0674     else {
0675         ui_configWidget->m_zoomToViewportCheckBox->setCheckState( Qt::Unchecked );
0676     }
0677 }
0678 
0679 void ElevationProfileFloatItem::writeSettings()
0680 {
0681     if ( ui_configWidget->m_zoomToViewportCheckBox->checkState() == Qt::Checked ) {
0682         m_zoomToViewport = true;
0683     } else {
0684         m_zoomToViewport = false;
0685     }
0686 
0687     emit settingsChanged( nameId() );
0688 }
0689 
0690 void ElevationProfileFloatItem::toggleZoomToViewport()
0691 {
0692     m_zoomToViewport = ! m_zoomToViewport;
0693     calculateStatistics( m_eleData );
0694     if ( ! m_zoomToViewport ) {
0695         m_axisX.setRange( m_eleData.first().x(), m_eleData.last().x() );
0696         m_axisY.setRange( qMin( m_minElevation, qreal( 0.0 ) ), m_maxElevation );
0697     }
0698     readSettings();
0699     emit settingsChanged( nameId() );
0700 }
0701 
0702 void ElevationProfileFloatItem::switchToRouteDataSource()
0703 {
0704     switchDataSource(&m_routeDataSource);
0705 }
0706 
0707 void ElevationProfileFloatItem::switchToTrackDataSource(int index)
0708 {
0709     m_trackDataSource.setSourceIndex(index);
0710     switchDataSource(&m_trackDataSource);
0711 }
0712 
0713 void ElevationProfileFloatItem::switchDataSource(ElevationProfileDataSource* source)
0714 {
0715     if (m_activeDataSource) {
0716         disconnect(m_activeDataSource, SIGNAL(dataUpdated(GeoDataLineString,QVector<QPointF>)),nullptr,nullptr);
0717     }
0718     m_activeDataSource = source;
0719     connect(m_activeDataSource, SIGNAL(dataUpdated(GeoDataLineString,QVector<QPointF>)), this, SLOT(handleDataUpdate(GeoDataLineString,QVector<QPointF>)));
0720     m_activeDataSource->requestUpdate();
0721 }
0722 
0723 }
0724 
0725 #include "moc_ElevationProfileFloatItem.cpp"