#include "lts_height_plot_widget.h" #include #include #include #include #include #include static inline double getLength(const QPointF &p1, const QPointF &p2) { const double x = p1.x() - p2.x(); const double y = p1.y() - p2.y(); return sqrt(x * x + y * y); } class LtsHeightPlotWidget::Private { public: Private() : m_length(0) , m_minElevation(0) , m_maxElevation(0) , m_nullX(0) , m_nullY(0) , m_scaleX(0) , m_scaleY(0) , m_fillBrush(Qt::gray) , m_axisPen(Qt::black) , m_mousePosPen(Qt::blue) , m_markerPen(Qt::magenta) , iPositionHint(1) { m_axisPen.setWidth(1); m_viewLinePen.setWidth(1); m_textFont.setPixelSize(10); m_markerPen.setWidth(2); m_markerPen.setCapStyle(Qt::RoundCap); m_mousePosPen.setStyle(Qt::DashDotDotLine); m_viewLinePen.setStyle(Qt::DashLine); } void clearPixmap(); double getDataHeight(double pos) const; QPointF getDataPos(double pos) const; int findDataPointIdx(const QPointF &point) const; void clear(); void recalcData(); void setData(const QVector > &data); void setMarks(const QVector &data); void resize(const QSize &newSize); void repaint(); void paintLines(QPainter &p) const; QVector< QPair< QPointF, double > > m_data; QVector< QPointF > m_marks; QVector< QPair< double, double > > m_heightList; double m_length; double m_minElevation; double m_maxElevation; double m_nullX; double m_nullY; double m_scaleX; double m_scaleY; QPainterPath m_drawPoints; QBrush m_fillBrush; QPen m_axisPen; QPen m_viewLinePen; QPen m_mousePosPen; QPen m_markerPen; QFont m_textFont; QPointF m_mousePos; QPointF m_mouseLeftClickPos; QSize m_curSize; QTransform m_trans1; QTransform m_trans2; QTransform m_transInverted; QTransform m_transCombined; QPixmap m_pixmap; mutable int iPositionHint; }; double LtsHeightPlotWidget::Private::getDataHeight(double pos) const { if (m_heightList.count() <= 1) return 0; if (pos <= 0.) return m_heightList[0].second; if (pos >= m_heightList[m_heightList.count() - 1].first) return m_heightList[m_heightList.count() - 1].second; if (iPositionHint >= m_heightList.size()) iPositionHint = 1; if (pos <= m_heightList[iPositionHint].first) { while (iPositionHint > 1 && pos <= m_heightList[iPositionHint - 1].first) --iPositionHint; } else { while (iPositionHint < m_heightList.count() - 1 && pos > m_heightList[iPositionHint].first) ++iPositionHint; } const int iFound = iPositionHint; const double x = m_heightList[iFound].first - m_heightList[iFound - 1].first; const double y = m_heightList[iFound].second - m_heightList[iFound - 1].second; const double m = y / x; const double n = m_heightList[iFound].second - m * m_heightList[iFound].first; return m * pos + n; } QPointF LtsHeightPlotWidget::Private::getDataPos(double pos) const { if (m_heightList.count() <= 1) return QPointF(); if (pos <= 0.) return m_data[0].first; if (pos >= m_heightList[m_heightList.count() - 1].first) return m_data[m_heightList.count() - 1].first; if (pos <= m_heightList[iPositionHint].first) { while (iPositionHint > 1 && pos <= m_heightList[iPositionHint - 1].first) --iPositionHint; } else { while (iPositionHint < m_heightList.count() - 1 && pos > m_heightList[iPositionHint].first) ++iPositionHint; } const int iFound = iPositionHint; const double diff = m_heightList[iFound].first - m_heightList[iFound - 1].first; double percentile = (pos - m_heightList[iFound - 1].first) / diff; const QPointF &p1 = m_data[iFound - 1].first; const QPointF p2 = m_data[iFound].first - m_data[iFound - 1].first; return QPointF(p1.x() + p2.x() * percentile, p1.y() + p2.y() * percentile); } int LtsHeightPlotWidget::Private::findDataPointIdx(const QPointF &point) const { for (int i = 0; i < m_heightList.count() - 1; ++i) { const double x = m_data[i + 1].first.x() - m_data[i].first.x(); const double y = m_data[i + 1].first.y() - m_data[i].first.y(); const double m = y / x; const double n = m_data[i].first.y() - m * m_data[i].first.x(); const double yRes = m * point.x() + n; if (fabs(yRes - point.y()) < 1.) { const double len = getLength(m_data[ i ].first, point); if (len <= m_heightList[i].first) return i; } } return -1; } void LtsHeightPlotWidget::Private::clear() { m_data.clear(); m_marks.clear(); m_length = 0; m_minElevation = 0; m_maxElevation = 0; m_scaleX = 0; m_scaleY = 0; m_drawPoints = QPainterPath(); m_heightList.clear(); m_curSize = QSize(); iPositionHint = 1; } void LtsHeightPlotWidget::Private::clearPixmap() { m_pixmap = QPixmap(); } inline double log5(double x) { return log10(x) / log10(5.); } void LtsHeightPlotWidget::Private::recalcData() { m_heightList.clear(); m_drawPoints = QPainterPath(); if (m_data.isEmpty()) { clear(); return; } double length = 0; double minElevation = m_data.at(0).second; double maxElevation = minElevation; for (int i = 1; i < m_data.count(); ++i) { length += getLength(m_data.at(i - 1).first, m_data.at(i).first); minElevation = std::min(minElevation, m_data.at(i).second); maxElevation = std::max(maxElevation, m_data.at(i).second); } if (minElevation < -10000.0) minElevation = -10000.0; if (maxElevation < -10000.0) maxElevation = -10000.0; if (minElevation > 10000.0) minElevation = 10000.0; if (maxElevation > 10000.0) maxElevation = 10000.0; if (qFuzzyCompare(minElevation, maxElevation)) { minElevation -= 1.0; maxElevation += 1.0; } double n2 = pow(5, floor(log5(fabs(minElevation))) - 1); if (qFuzzyIsNull(n2)) n2 = 0.1; m_minElevation = lrint(floor(minElevation / n2) * n2); m_maxElevation = lrint(ceil(maxElevation / n2) * n2); m_scaleY = pow(10, floor(log10(m_maxElevation - m_minElevation)) - 1); if (m_scaleY < 0.1) m_scaleY = 0.1; m_nullY = m_minElevation; if (m_minElevation < 0 && m_maxElevation > 0) m_nullY = 0; const double n = pow(10, floor(log10(length))) / 2; m_length = lrint(ceil(length / n) * n); m_scaleX = pow(10, floor(log10(length)) - 1); if (m_scaleX < 0.1) m_scaleX = 0.1; m_nullX = 0; double len = 0; m_drawPoints.moveTo(len, m_data.at(0).second); m_heightList.append(QPair(len, m_data.at(0).second)); for (int i = 1; i < m_data.count(); ++i) { len += getLength(m_data.at(i - 1).first, m_data.at(i).first); m_drawPoints.lineTo(len, m_data.at(i).second); m_heightList.append(QPair(len, m_data.at(i).second)); } m_drawPoints.lineTo(len, m_minElevation); m_drawPoints.lineTo(0, m_minElevation); m_drawPoints.setFillRule(Qt::WindingFill); } void LtsHeightPlotWidget::Private::setData(const QVector< QPair< QPointF, double > > &data) { if (data.isEmpty()) { clear(); return; } QSize tmp = m_curSize; clear(); m_data = data; recalcData(); resize(tmp); } void LtsHeightPlotWidget::Private::setMarks(const QVector< QPointF > &data) { m_marks = data; recalcData(); repaint(); } void LtsHeightPlotWidget::Private::resize(const QSize &newSize) { if (newSize == m_curSize) return; m_curSize = newSize; const int border = 25; const double width = m_curSize.width(); const double height = m_curSize.height(); const double minY = m_minElevation - m_scaleY; const double maxY = m_maxElevation + m_scaleY; const double wertebereichY = maxY - minY; const double minX = 0 - m_scaleX; const double maxX = m_length + m_scaleX; const double wertebereichX = maxX - minX; m_trans1.reset(); m_trans2.reset(); m_trans1.translate(10, height); m_trans1.scale(1, -1); m_trans1.translate(border, border); m_trans1.scale(1, -1); m_trans2.scale((width - 2 * border) / (wertebereichX), (height - 2 * border) / (wertebereichY)); m_trans2.translate(0, minY); m_trans2.scale(1, -1); m_transInverted = m_trans1.inverted() * m_trans2.inverted(); m_transCombined = m_trans2 * m_trans1; repaint(); } void LtsHeightPlotWidget::Private::repaint() { if (!m_curSize.isValid()) return; if (!m_heightList.count()) return; QImage img(m_curSize, QImage::Format_ARGB32); img.fill(Qt::transparent); QPainter p(&img); const QPainter::RenderHints hints = QPainter::HighQualityAntialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing | QPainter::Antialiasing; p.setRenderHints(hints); p.setTransform(m_trans1); p.setPen(QPen(m_fillBrush, 0.1)); p.setBrush(m_fillBrush); p.drawPath(m_trans2.map(m_drawPoints)); p.setRenderHint(QPainter::Antialiasing, false); p.setPen(m_markerPen); for (int i = 0; i < m_marks.count(); ++i) { int posIdx = findDataPointIdx(m_marks.at(i)); if (posIdx < 0) continue; double len = getLength(m_data.at(posIdx).first, m_marks.at(i)); len += m_heightList.at(posIdx).first; const double height = getDataHeight(len); const QLineF l(len, m_minElevation, len, height); p.drawLine(m_trans2.map(l)); } p.setPen(m_axisPen); p.setFont(m_textFont); p.drawLine(m_trans2.map(QPointF(m_nullX, m_minElevation - m_scaleY)), m_trans2.map(QPointF(m_nullX, m_maxElevation + m_scaleY))); for (double i = m_minElevation; i <= m_maxElevation; i += m_scaleY) { const qreal y = m_trans2.map(QPointF(0, i)).y(); p.drawLine(QLineF(-2, y, 2, y)); } for (double i = m_minElevation; i <= m_maxElevation; i += m_scaleY * 5) { const qreal y = m_trans2.map(QPointF(0, i)).y(); p.drawLine(QLineF(-4, y, 4, y)); } QFontMetricsF fm(m_textFont); for (double i = m_minElevation; i <= m_maxElevation; i += m_scaleY * 5) { if (qFuzzyCompare(i, m_nullY)) continue; const qreal y = m_trans2.map(QPointF(m_nullX, i)).y(); const QPointF p1(-20, y); const QString str = QString::number(i); QRectF r = fm.boundingRect(str); r.setWidth(fm.width(str)); r.moveTo(p1.x() - r.width() / 2., p1.y() - r.height() / 2.); p.drawText(r, str, Qt::AlignHCenter | Qt::AlignVCenter); } p.drawLine(m_trans2.map(QPointF(-m_scaleX, m_nullY)), m_trans2.map(QPointF(m_scaleX + m_length, m_nullY))); for (double i = m_scaleX; i <= m_length; i += m_scaleX) { const QPointF point = m_trans2.map(QPointF(i, m_nullY)); p.drawLine(QLineF(point.x(), point.y() - 2, point.x(), point.y() + 2)); } for (double i = 0; i <= m_length; i += m_scaleX * 5) { const QPointF point = m_trans2.map(QPointF(i, m_nullY)); p.drawLine(QLineF(point.x(), point.y() - 4, point.x(), point.y() + 4)); } for (double i = 0; i <= m_length; i += m_scaleX * 5) { if (qFuzzyCompare(i, m_nullX)) continue; const QPointF point = m_trans2.map(QPointF(i, m_nullY)); const QPointF p1(point.x(), point.y() + 15); const QString str = QString::number(i); QRectF r = fm.boundingRect(str); r.setWidth(fm.width(str)); r.moveTo(p1.x() - r.width() / 2., p1.y() - r.height() / 2.); p.drawText(r, str, Qt::AlignHCenter | Qt::AlignVCenter); } p.end(); m_pixmap = QPixmap::fromImage(img); } void LtsHeightPlotWidget::Private::paintLines(QPainter &p) const { if (m_heightList.count() > 0) { QPointF pos = m_mousePos; if (pos.x() < 0) pos.setX(0); if (pos.x() > m_heightList.at(m_heightList.count() - 1).first) pos.setX(m_heightList.at(m_heightList.count() - 1).first); const double curHeight = getDataHeight(pos.x()); p.setPen(m_mousePosPen); p.drawLine(m_transCombined.map(QLineF(m_nullX, curHeight, pos.x(), curHeight))); p.drawLine(m_transCombined.map(QLineF(pos.x(), m_nullY, pos.x(), curHeight))); if (!m_mouseLeftClickPos.isNull()) { const double y = curHeight > pos.y() ? curHeight : pos.y(); p.drawLine(m_transCombined.map(QLineF(m_nullX, y, pos.x(), y))); p.drawLine(m_transCombined.map(QLineF(pos.x(), m_nullY, pos.x(), y))); p.setPen(m_viewLinePen); const QPointF &c = m_mouseLeftClickPos; p.drawLine(m_transCombined.map(QLineF(m_nullX, c.y(), c.x(), c.y()))); p.drawLine(m_transCombined.map(QLineF(c.x(), m_nullY, c.x(), c.y()))); const QLineF line(m_mouseLeftClickPos, QPointF(pos.x(), y)); p.drawLine(m_transCombined.map(line)); } } } LtsHeightPlotWidget::LtsHeightPlotWidget(QWidget *parent) : QWidget(parent) , d(new Private) { setMinimumSize(300, 300); resize(size()); setMouseTracking(true); } LtsHeightPlotWidget::~LtsHeightPlotWidget() { delete d; } void LtsHeightPlotWidget::clearView() { d->clearPixmap(); update(); } void LtsHeightPlotWidget::setData(const QVector< QPair< QPointF, double > > &data) { d->setData(data); update(); } void LtsHeightPlotWidget::setMarks(const QVector &data) { d->setMarks(data); } void LtsHeightPlotWidget::setBackgroundBrush(const QBrush &brush) { QPalette pal = palette(); pal.setBrush(QPalette::Background, brush); setPalette(pal); } void LtsHeightPlotWidget::setTextFont(const QFont &f) { d->m_textFont = f; d->repaint(); } void LtsHeightPlotWidget::setAxisPen(const QPen &pen) { d->m_axisPen = pen; d->repaint(); } void LtsHeightPlotWidget::setPositionPen(const QPen &pen) { d->m_mousePosPen = pen; } void LtsHeightPlotWidget::setViewLinePen(const QPen &pen) { d->m_viewLinePen = pen; } void LtsHeightPlotWidget::setContourBrush(const QBrush &brush) { d->m_fillBrush = brush; d->repaint(); } void LtsHeightPlotWidget::setMarkerPen(const QPen &pen) { d->m_markerPen = pen; d->repaint(); } void LtsHeightPlotWidget::resizeEvent(QResizeEvent *e) { d->resize(size()); QWidget::resizeEvent(e); } void LtsHeightPlotWidget::paintEvent(QPaintEvent *e) { Q_UNUSED(e); QPainter p(this); const QPainter::RenderHints hints = QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing | QPainter::Antialiasing; p.setRenderHints(hints); p.drawPixmap(0, 0, d->m_pixmap); d->paintLines(p); p.end(); } void LtsHeightPlotWidget::mouseMoveEvent(QMouseEvent *e) { QWidget::mouseMoveEvent(e); if (!d->m_heightList.count()) return; const QPointF pos = d->m_transInverted.map(e->localPos()); emit mousePositionChanged(d->getDataPos(pos.x()), d->getDataHeight(pos.x()), pos.y()); d->m_mousePos = pos; update(); } void LtsHeightPlotWidget::mouseReleaseEvent(QMouseEvent *e) { QWidget::mouseReleaseEvent(e); if (!d->m_heightList.count()) return; if (e->button() == Qt::LeftButton) { const QPointF &pos = d->m_mousePos; if (pos.x() < 0) return; if (pos.x() > d->m_heightList.at(d->m_heightList.count() - 1).first) return; const double curHeight = d->getDataHeight(pos.x()); d->m_mouseLeftClickPos = QPointF(pos.x(), curHeight > pos.y() ? curHeight : pos.y()); emit startLineOfSight(d->getDataPos(pos.x())); } else if (e->button() == Qt::RightButton) { d->m_mouseLeftClickPos = QPointF(); emit stopLineOfSight(); update(); } }