mirror of
https://github.com/tumic0/GPXSee.git
synced 2025-01-19 04:02:09 +01:00
432 lines
9.0 KiB
C++
432 lines
9.0 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 "markerinfoitem.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
|
|
|
|
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);
|
|
}
|
|
|
|
Units PathItem::_units = Metric;
|
|
QTimeZone PathItem::_timeZone = QTimeZone::utc();
|
|
|
|
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;
|
|
QBrush brush(Qt::SolidPattern);
|
|
_pen = QPen(brush, _width);
|
|
_showMarker = true;
|
|
_showTicks = false;
|
|
|
|
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);
|
|
}
|
|
|
|
void PathItem::setColor(const QColor &color)
|
|
{
|
|
if (_pen.color() == color)
|
|
return;
|
|
|
|
_pen.setColor(color);
|
|
|
|
for (int i = 0; i < _ticks.size(); i++)
|
|
_ticks[i]->setColor(color);
|
|
|
|
update();
|
|
}
|
|
|
|
void PathItem::setWidth(qreal width)
|
|
{
|
|
if (_width == width)
|
|
return;
|
|
|
|
prepareGeometryChange();
|
|
|
|
_width = width;
|
|
_pen.setWidthF(_width * pow(2, -_digitalZoom));
|
|
|
|
updateShape();
|
|
}
|
|
|
|
void PathItem::setStyle(Qt::PenStyle style)
|
|
{
|
|
if (_pen.style() == style)
|
|
return;
|
|
|
|
_pen.setStyle(style);
|
|
update();
|
|
}
|
|
|
|
void PathItem::setDigitalZoom(int zoom)
|
|
{
|
|
if (_digitalZoom == zoom)
|
|
return;
|
|
|
|
prepareGeometryChange();
|
|
|
|
_digitalZoom = zoom;
|
|
_pen.setWidthF(_width * pow(2, -_digitalZoom));
|
|
_marker->setScale(pow(2, -_digitalZoom));
|
|
|
|
updateShape();
|
|
}
|
|
|
|
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)
|
|
{
|
|
qreal time = _graph
|
|
? (_graph->graphType() == Time) ? pos : _graph->timeAtDistance(pos)
|
|
: NAN;
|
|
QDateTime d(date());
|
|
|
|
if (!std::isnan(time) && d.isValid())
|
|
_markerInfo->setDate(d.addSecs(time).toTimeZone(_timeZone));
|
|
}
|
|
|
|
void PathItem::setMarkerColor(const QColor &color)
|
|
{
|
|
_marker->setColor(color);
|
|
_markerInfo->setColor(color);
|
|
}
|
|
|
|
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;
|
|
_marker->setVisible(show && isValid(position(_markerDistance)));
|
|
}
|
|
|
|
void PathItem::showMarkerInfo(bool show)
|
|
{
|
|
_markerInfo->setVisible(show);
|
|
}
|
|
|
|
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, SIGNAL(selected(bool)), graph, SLOT(hover(bool)));
|
|
connect(graph, SIGNAL(selected(bool)), this, SLOT(hover(bool)));
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|