1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2025-02-26 04:00:49 +01:00
GPXSee/src/GUI/pathitem.cpp
Martin Tůma f683525fce Fixed/improved map info background rendering settings
All text items on the map (including path marker info) have now the same
background color (the map background color) and the "info background" option
also applies to all items now.
2023-08-19 11:01:12 +02:00

516 lines
11 KiB
C++

#include <cmath>
#include <QCursor>
#include <QPainter>
#include <QGraphicsSceneMouseEvent>
#include "common/greatcircle.h"
#include "map/map.h"
#include "pathtickitem.h"
#include "popup.h"
#include "graphitem.h"
#include "markeritem.h"
#include "pathitem.h"
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
#define INTERSECTS intersect
#else // QT 5.15
#define INTERSECTS intersects
#endif // QT 5.15
#define GEOGRAPHICAL_MILE 1855.3248
Units PathItem::_units = Metric;
QTimeZone PathItem::_timeZone = QTimeZone::utc();
static inline bool isValid(const QPointF &p)
{
return (!std::isnan(p.x()) && !std::isnan(p.y()));
}
static inline unsigned segments(qreal distance)
{
return ceil(distance / GEOGRAPHICAL_MILE);
}
PathItem::PathItem(const Path &path, Map *map, QGraphicsItem *parent)
: GraphicsItem(parent), _path(path), _map(map), _graph(0)
{
Q_ASSERT(_path.isValid());
_digitalZoom = 0;
_width = 3;
_color = Qt::black;
_penStyle = Qt::SolidLine;
_showMarker = true;
_showTicks = false;
_markerInfoType = MarkerInfoItem::None;
_pen = QPen(color(), width());
updatePainterPath();
updateShape();
updateTicks();
_markerDistance = _path.first().first().distance();
_marker = new MarkerItem(this);
_marker->setZValue(1);
_marker->setPos(position(_markerDistance));
_markerInfo = new MarkerInfoItem(_marker);
_markerInfo->setVisible(false);
setCursor(Qt::ArrowCursor);
setAcceptHoverEvents(true);
}
void PathItem::updateShape()
{
QPainterPathStroker s;
s.setWidth((_width + 1) * pow(2, -_digitalZoom));
_shape = s.createStroke(_painterPath);
}
void PathItem::addSegment(const Coordinates &c1, const Coordinates &c2)
{
if (fabs(c1.lon() - c2.lon()) > 180.0) {
// Split segment on date line crossing
QPointF p;
if (c2.lon() < 0) {
QLineF l(QPointF(c1.lon(), c1.lat()), QPointF(c2.lon() + 360,
c2.lat()));
QLineF dl(QPointF(180, -90), QPointF(180, 90));
l.INTERSECTS(dl, &p);
_painterPath.lineTo(_map->ll2xy(Coordinates(180, p.y())));
_painterPath.moveTo(_map->ll2xy(Coordinates(-180, p.y())));
} else {
QLineF l(QPointF(c1.lon(), c1.lat()), QPointF(c2.lon() - 360,
c2.lat()));
QLineF dl(QPointF(-180, -90), QPointF(-180, 90));
l.INTERSECTS(dl, &p);
_painterPath.lineTo(_map->ll2xy(Coordinates(-180, p.y())));
_painterPath.moveTo(_map->ll2xy(Coordinates(180, p.y())));
}
_painterPath.lineTo(_map->ll2xy(c2));
} else
_painterPath.lineTo(_map->ll2xy(c2));
}
void PathItem::updatePainterPath()
{
_painterPath = QPainterPath();
for (int i = 0; i < _path.size(); i++) {
const PathSegment &segment = _path.at(i);
_painterPath.moveTo(_map->ll2xy(segment.first().coordinates()));
for (int j = 1; j < segment.size(); j++) {
const PathPoint &p1 = segment.at(j-1);
const PathPoint &p2 = segment.at(j);
unsigned n = segments(p2.distance() - p1.distance());
if (n > 1) {
GreatCircle gc(p1.coordinates(), p2.coordinates());
Coordinates last = p1.coordinates();
for (unsigned k = 1; k <= n; k++) {
Coordinates c(gc.pointAt(k/(double)n));
addSegment(last, c);
last = c;
}
} else
addSegment(p1.coordinates(), p2.coordinates());
}
}
}
void PathItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setPen(_pen);
painter->drawPath(_painterPath);
/*
painter->setPen(Qt::red);
painter->drawRect(boundingRect());
*/
}
void PathItem::setMap(Map *map)
{
prepareGeometryChange();
_map = map;
updatePainterPath();
updateShape();
updateTicks();
QPointF pos = position(_markerDistance);
if (isValid(pos))
_marker->setPos(pos);
}
const QColor &PathItem::color() const
{
return (_useStyle && _path.style().color().isValid())
? _path.style().color() : _color;
}
void PathItem::setColor(const QColor &color)
{
_color = color;
updateColor();
}
void PathItem::updateColor()
{
const QColor &c(color());
_pen.setColor(c);
for (int i = 0; i < _ticks.size(); i++)
_ticks[i]->setColor(c);
update();
}
qreal PathItem::width() const
{
return (_useStyle && _path.style().width() > 0)
? _path.style().width() : _width;
}
void PathItem::setWidth(qreal width)
{
_width = width;
updateWidth();
}
void PathItem::updateWidth()
{
prepareGeometryChange();
_pen.setWidthF(width() * pow(2, -_digitalZoom));
updateShape();
}
Qt::PenStyle PathItem::penStyle() const
{
return (_useStyle && _path.style().style() != Qt::NoPen)
? _path.style().style() : _penStyle;
}
void PathItem::setPenStyle(Qt::PenStyle style)
{
_penStyle = style;
updatePenStyle();
}
void PathItem::updatePenStyle()
{
_pen.setStyle(penStyle());
update();
}
void PathItem::setDigitalZoom(int zoom)
{
if (_digitalZoom == zoom)
return;
prepareGeometryChange();
_digitalZoom = zoom;
_pen.setWidthF(width() * pow(2, -_digitalZoom));
_marker->setScale(pow(2, -_digitalZoom));
for (int i = 0; i < _ticks.size(); i++)
_ticks.at(i)->setDigitalZoom(zoom);
updateShape();
}
void PathItem::updateStyle()
{
updateColor();
updateWidth();
updatePenStyle();
for (int i = 0; i < _graphs.size(); i++) {
GraphItem *graph = _graphs.at(i);
if (graph)
graph->updateStyle();
}
}
const PathSegment *PathItem::segment(qreal x) const
{
for (int i = 0; i < _path.size(); i++)
if (x <= _path.at(i).last().distance())
return &(_path.at(i));
return 0;
}
QPointF PathItem::position(qreal x) const
{
const PathSegment *seg = segment(x);
if (!seg)
return QPointF(NAN, NAN);
int low = 0;
int high = seg->count() - 1;
int mid = 0;
if (!(x >= seg->first().distance() && x <= seg->last().distance()))
return QPointF(NAN, NAN);
while (low <= high) {
mid = low + ((high - low) / 2);
qreal val = seg->at(mid).distance();
if (val > x)
high = mid - 1;
else if (val < x)
low = mid + 1;
else
return _map->ll2xy(seg->at(mid).coordinates());
}
Coordinates c1, c2;
qreal p1, p2;
if (seg->at(mid).distance() < x) {
c1 = seg->at(mid).coordinates(); c2 = seg->at(mid+1).coordinates();
p1 = seg->at(mid).distance(); p2 = seg->at(mid+1).distance();
} else {
c1 = seg->at(mid-1).coordinates(); c2 = seg->at(mid).coordinates();
p1 = seg->at(mid-1).distance(); p2 = seg->at(mid).distance();
}
unsigned n = segments(p2 - p1);
if (n > 1) {
GreatCircle gc(c1, c2);
// Great circle point
double f = (x - p1) / (p2 - p1);
QPointF p(_map->ll2xy(gc.pointAt(f)));
// Segment line of the great circle path
double f1 = floor(n * f) / n;
double f2 = ceil(n * f) / n;
QLineF l(_map->ll2xy(gc.pointAt(f1)), _map->ll2xy(gc.pointAt(f2)));
// Project the great circle point to the segment line
QLineF u = l.unitVector();
double lambda = (u.dx() * (p.x() - l.p1().x())) + (u.dy() * (p.y()
- l.p1().y()));
return QPointF((u.dx() * lambda) + l.p1().x(), (u.dy() * lambda)
+ l.p1().y());
} else {
QLineF l(_map->ll2xy(c1), _map->ll2xy(c2));
return l.pointAt((x - p1) / (p2 - p1));
}
}
void PathItem::setMarkerPosition(qreal pos)
{
qreal distance = _graph
? (_graph->graphType() == Time) ? _graph->distanceAtTime(pos) : pos
: NAN;
_markerDistance = distance;
QPointF pp(position(distance));
if (isValid(pp)) {
_marker->setVisible(_showMarker);
_marker->setPos(pp);
setMarkerInfo(pos);
} else
_marker->setVisible(false);
}
void PathItem::setMarkerInfo(qreal pos)
{
if (_markerInfoType == MarkerInfoItem::Date) {
QDateTime date;
if (_graph) {
qreal time = (_graph->graphType() == Time)
? pos : _graph->timeAtDistance(pos);
GraphItem::SegmentTime st(_graph->date(pos));
if (st.date.isValid() && !std::isnan(time))
date = st.date.addSecs(time - st.time);
}
if (date.isValid())
_markerInfo->setDate(date.toTimeZone(_timeZone));
else
_markerInfo->setDate(QDateTime());
} else if (_markerInfoType == MarkerInfoItem::Position)
_markerInfo->setCoordinates(_map->xy2ll(_marker->pos()));
}
void PathItem::updateMarkerInfo()
{
qreal pos = _graph ? (_graph->graphType() == Time)
? _graph->timeAtDistance(_markerDistance) : _markerDistance : NAN;
setMarkerInfo(pos);
}
void PathItem::setMarkerColor(const QColor &color)
{
_marker->setColor(color);
_markerInfo->setColor(color);
}
void PathItem::setMarkerBackgroundColor(const QColor &color)
{
_markerInfo->setBackgroundColor(color);
}
void PathItem::drawMarkerBackground(bool draw)
{
_markerInfo->drawBackground(draw);
}
void PathItem::hover(bool hover)
{
if (hover) {
_pen.setWidth((width() + 1) * pow(2, -_digitalZoom));
setZValue(zValue() + 1.0);
} else {
_pen.setWidth(width() * pow(2, -_digitalZoom));
setZValue(zValue() - 1.0);
}
update();
}
void PathItem::showMarker(bool show)
{
if (_showMarker == show)
return;
_showMarker = show;
updateMarkerInfo();
_marker->setVisible(show && isValid(position(_markerDistance)));
}
void PathItem::showMarkerInfo(MarkerInfoItem::Type type)
{
if (_markerInfoType == type)
return;
_markerInfoType = type;
updateMarkerInfo();
_markerInfo->setVisible(type > MarkerInfoItem::None);
}
qreal PathItem::xInM() const
{
return (_units == Nautical) ? NMIINM : (_units == Imperial) ? MIINM : KMINM;
}
unsigned PathItem::tickSize() const
{
qreal res = _map->resolution(sceneBoundingRect());
if (res < 10)
return 1;
else if (res < 25)
return 5;
else if (res < 100)
return 10;
else if (res < 500)
return 50;
else if (res < 2000)
return 100;
else if (res < 10000)
return 500;
else if (res < 20000)
return 1000;
else
return 5000;
}
void PathItem::updateTicks()
{
qDeleteAll(_ticks);
_ticks.clear();
if (!_showTicks)
return;
int ts = tickSize();
int tc = _path.last().last().distance() / (ts * xInM());
QRect tr = PathTickItem::tickRect(ts * tc);
_ticks.resize(tc);
for (int i = 0; i < tc; i++) {
_ticks[i] = new PathTickItem(tr, (i + 1) * ts, this);
_ticks[i]->setPos(position((i + 1) * ts * xInM()));
_ticks[i]->setColor(_pen.color());
}
}
void PathItem::showTicks(bool show)
{
if (_showTicks == show)
return;
prepareGeometryChange();
_showTicks = show;
updateTicks();
}
void PathItem::addGraph(GraphItem *graph)
{
_graphs.append(graph);
if (graph) {
connect(this, &PathItem::selected, graph, &GraphItem::hover);
connect(graph, &GraphItem::selected, this, &PathItem::hover);
if (graph->secondaryGraph()) {
connect(this, &PathItem::selected, graph->secondaryGraph(),
&GraphItem::hover);
connect(graph->secondaryGraph(), &GraphItem::selected, this,
&PathItem::hover);
}
}
}
void PathItem::setGraph(int index)
{
_graph = _graphs.at(index);
}
void PathItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
Q_UNUSED(event);
_pen.setWidthF((width() + 1) * pow(2, -_digitalZoom));
setZValue(zValue() + 1.0);
update();
emit selected(true);
}
void PathItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
Q_UNUSED(event);
_pen.setWidthF(width() * pow(2, -_digitalZoom));
setZValue(zValue() - 1.0);
update();
emit selected(false);
}
void PathItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
Popup::show(event->screenPos(), info(), event->widget());
GraphicsItem::mousePressEvent(event);
}