KOSMIndoorMap

mapitem.cpp
1/*
2 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "mapitem.h"
8#include "osmelement.h"
9
10#include <KOSMIndoorMap/HitDetector>
11#include <KOSMIndoorMap/MapCSSLoader>
12#include <KOSMIndoorMap/MapCSSParser>
13#include <KOSMIndoorMap/OverlaySource>
14
15#include <QDebug>
16#include <QGuiApplication>
17#include <QPainter>
18#include <QPalette>
19#include <QQuickWindow>
20#include <QTimeZone>
21
22using namespace KOSMIndoorMap;
23
24MapItem::MapItem(QQuickItem *parent)
25 : QQuickPaintedItem(parent)
26 , m_loader(new MapLoader(this))
27 , m_view(new View(this))
28 , m_floorLevelModel(new FloorLevelModel(this))
29{
30 connect(m_loader, &MapLoader::isLoadingChanged, this, &MapItem::clear);
31 connect(m_loader, &MapLoader::done, this, &MapItem::loaderDone);
32
33 m_view->setScreenSize({100, 100}); // FIXME this breaks view when done too late!
34 m_controller.setView(m_view);
35 connect(m_view, &View::floorLevelChanged, this, [this]() { update(); });
36 connect(m_view, &View::transformationChanged, this, [this]() { update(); });
37
38 setStylesheetName({}); // set default stylesheet
39
41}
42
43MapItem::~MapItem() = default;
44
45void MapItem::paint(QPainter *painter)
46{
47 m_controller.updateScene(m_sg);
48 m_renderer.setPainter(painter);
49 m_renderer.render(m_sg, m_view);
50}
51
52MapLoader* MapItem::loader() const
53{
54 return m_loader;
55}
56
57View* MapItem::view() const
58{
59 return m_view;
60}
61
62QString MapItem::styleSheetName() const
63{
64 return m_styleSheetUrl.toString();
65}
66
67void MapItem::setStylesheetName(const QString &styleSheet)
68{
69 QUrl styleFile = MapCSSLoader::resolve(styleSheet);
70 if (m_styleSheetUrl == styleFile) {
71 return;
72 }
73 m_styleSheetUrl = styleFile;
74 m_style = MapCSSStyle();
75
76 if (m_styleLoader) { // cancel an ongoing style load
77 disconnect(m_styleLoader, nullptr, this, nullptr);
78 delete m_styleLoader;
79 m_styleLoader = nullptr;
80 }
81
82 m_styleLoader = new MapCSSLoader(m_styleSheetUrl, KOSMIndoorMap::defaultNetworkAccessManagerFactory, this);
83 connect(m_styleLoader, &MapCSSLoader::finished, this, [this]() {
84 if (m_styleLoader->hasError()) {
85 m_errorMessage = m_styleLoader->errorMessage();
86 } else {
87 m_style = m_styleLoader->takeStyle();
88 m_errorMessage.clear();
89
90 m_style.compile(m_data.dataSet());
91 m_controller.setStyleSheet(&m_style);
92 update();
93 }
94 Q_EMIT errorChanged();
95 m_styleLoader->deleteLater();
96 m_styleLoader = nullptr;
97 });
98 m_styleLoader->start();
99
100 Q_EMIT styleSheetChanged();
101}
102
103FloorLevelModel* MapItem::floorLevelModel() const
104{
105 return m_floorLevelModel;
106}
107
108void MapItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
109{
110 QQuickPaintedItem::geometryChange(newGeometry, oldGeometry);
111 m_view->setScreenSize(newGeometry.size().toSize());
112 // the scale factor isn't automatically applied to the paint device, only to the input coordinates
113 // so we need to handle this manually here
114 if (window()) {
115 m_view->setDeviceTransform(QTransform::fromScale(window()->devicePixelRatio(), window()->devicePixelRatio()));
116 }
117}
118
119void MapItem::loaderDone()
120{
121 m_floorLevelModel->setMapData(nullptr);
122 m_sg.clear();
123
124 if (!m_loader->hasError()) {
125 auto data = m_loader->takeData();
126 if (data.regionCode().isEmpty()) {
127 data.setRegionCode(m_data.regionCode());
128 }
129 data.setTimeZone(m_data.timeZone());
130 m_data = std::move(data);
131 m_view->setSceneBoundingBox(m_data.boundingBox());
132 m_controller.setMapData(m_data);
133 m_style.compile(m_data.dataSet());
134 m_controller.setStyleSheet(&m_style);
135 m_view->setLevel(0);
136 m_floorLevelModel->setMapData(&m_data);
137 m_view->floorLevelChanged();
138 Q_EMIT mapDataChanged();
139 }
140
141 Q_EMIT errorChanged();
142 update();
143}
144
145OSMElement MapItem::elementAt(double x, double y) const
146{
147 HitDetector detector;
148 const auto item = detector.itemAt(QPointF(x, y), m_sg, m_view);
149 if (item) {
150 qDebug() << item->element.url();
151 for (auto it = item->element.tagsBegin(); it != item->element.tagsEnd(); ++it) {
152 qDebug() << " " << (*it).key.name() << (*it).value;
153 }
154 return OSMElement(item->element);
155 }
156 return {};
157}
158
159void MapItem::clear()
160{
161 if (!m_loader->isLoading() || m_sg.items().empty()) {
162 return;
163 }
164
165 m_sg.clear();
166 m_data = MapData();
167 m_controller.setMapData(m_data);
168 Q_EMIT mapDataChanged();
169 Q_EMIT errorChanged();
170 update();
171}
172
173bool MapItem::hasError() const
174{
175 return !m_errorMessage.isEmpty() || m_loader->hasError();
176}
177
179{
180 return m_errorMessage.isEmpty() ? m_loader->errorMessage() : m_errorMessage;
181}
182
183MapData MapItem::mapData() const
184{
185 return m_data;
186}
187
189{
190 return m_overlaySources;
191}
192
193void MapItem::setOverlaySources(const QVariant &overlays)
194{
195 const auto oldOwnedOverlays = std::move(m_ownedOverlaySources);
196
197 std::vector<QPointer<AbstractOverlaySource>> sources;
198 if (overlays.canConvert<QVariantList>()) {
199 const auto l = overlays.value<QVariantList>();
200 for (const auto &v : l) {
201 addOverlaySource(sources, v);
202 }
203 } else {
204 addOverlaySource(sources, overlays);
205 }
206
207 for (const auto &overlay : sources) {
208 connect(overlay.data(), &AbstractOverlaySource::update, this, &MapItem::overlayUpdate, Qt::UniqueConnection);
209 connect(overlay.data(), &AbstractOverlaySource::reset, this, &MapItem::overlayReset, Qt::UniqueConnection);
210 }
211
212 m_controller.setOverlaySources(std::move(sources));
213 Q_EMIT overlaySourcesChanged();
214 update();
215}
216
217void MapItem::addOverlaySource(std::vector<QPointer<AbstractOverlaySource>> &overlaySources, const QVariant &source)
218{
219 const auto obj = source.value<QObject*>();
220 if (auto model = qobject_cast<QAbstractItemModel*>(obj)) {
221 auto overlay = std::make_unique<ModelOverlaySource>(model);
222 overlaySources.push_back(overlay.get());
223 m_ownedOverlaySources.push_back(std::move(overlay));
224 } else if (auto source = qobject_cast<AbstractOverlaySource*>(obj)) {
225 overlaySources.push_back(source);
226 } else {
227 qWarning() << "unsupported overlay source:" << source << obj;
228 }
229}
230
231void MapItem::overlayUpdate()
232{
233 m_controller.overlaySourceUpdated();
234 update();
235}
236
237void MapItem::overlayReset()
238{
239 m_style.compile(m_data.dataSet());
240}
241
243{
244 return m_data.regionCode();
245}
246
247void MapItem::setRegion(const QString &region)
248{
249 if (m_data.regionCode() == region) {
250 return;
251 }
252
253 m_data.setRegionCode(region);
254 Q_EMIT regionChanged();
255}
256
257QString MapItem::timeZoneId() const
258{
259 return QString::fromUtf8(m_data.timeZone().id());
260}
261
262void MapItem::setTimeZoneId(const QString &tz)
263{
264 const auto tzId = tz.toUtf8();
265 if (m_data.timeZone().id() == tzId) {
266 return;
267 }
268
269 m_data.setTimeZone(QTimeZone(tzId));
270 Q_EMIT timeZoneChanged();
271}
272
274{
275 return OSMElement(m_controller.hoveredElement());
276}
277
278void MapItem::setHoveredElement(const OSMElement &element)
279{
280 if (m_controller.hoveredElement() == element.element()) {
281 return;
282 }
283 m_controller.setHoveredElement(element.element());
284 Q_EMIT hoveredElementChanged();
285 update();
286}
287
288#include "moc_mapitem.cpp"
void reset()
Trigger style re-compilation.
void update()
Trigger map re-rendering when the source changes.
Picking hit detector.
Definition hitdetector.h:30
const SceneGraphItem * itemAt(QPointF pos, const SceneGraph &sg, const View *view) const
Highest (in z-order) item at the given screen position.
Asynchronous loader for (remote) MapCSS assets.
void start()
Start loading.
bool hasError() const
Check whether loading or parsing failed in some way.
static QUrl resolve(const QString &style, const QUrl &baseUrl={})
Resolve style to an absolute URL to load.
static void expire()
Expire locally cached remote MapCSS assets.
MapCSSStyle && takeStyle()
The fully loaded and parsed style.
void finished()
Loading is done, successfully or with an error.
A parsed MapCSS style sheet.
Definition mapcssstyle.h:33
void compile(OSM::DataSet &dataSet)
Optimizes style sheet rules for application against dataSet.
Raw OSM map data, separated by levels.
Definition mapdata.h:60
bool hasError
There's a loading error (data not found, network issue, broken style sheet, etc).
Definition mapitem.h:40
KOSMIndoorMap::OSMElement hoveredElement
Currently hovered element.
Definition mapitem.h:60
QVariant overlaySources
Sources for overlays that should be rendered on top of the map.
Definition mapitem.h:47
QString region
ISO 3166-1/2 country or region code this map area is in.
Definition mapitem.h:52
QString errorMessage
Details on the error.
Definition mapitem.h:42
Loader for OSM data for a single station or airport.
Definition maploader.h:29
void done()
Emitted when the requested data has been loaded.
bool isLoading
Indicates we are downloading content.
Definition maploader.h:32
MapData && takeData()
Take out the completely loaded result.
QML wrapper around an OSM element.
Definition osmelement.h:21
OSM::Element hoveredElement() const
Set currently hovered element.
void overlaySourceUpdated()
Overlay dirty state tracking.
void updateScene(SceneGraph &sg) const
Creates or updates sg based on the currently set style and view settings.
void clear()
Clears all data from the scene graph.
void update(Part *part, const QByteArray &data, qint64 dataSize)
OSM-based multi-floor indoor maps for buildings.
KOSMINDOORMAP_EXPORT QNetworkAccessManager * defaultNetworkAccessManagerFactory()
Default implementation if not using an application-specific QNetworkAccessManager instance.
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
bool disconnect(const QMetaObject::Connection &connection)
T qobject_cast(QObject *object)
virtual void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
void update()
QQuickWindow * window() const const
QSizeF size() const const
QSize toSize() const const
void clear()
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QByteArray toUtf8() const const
UniqueConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QTransform fromScale(qreal sx, qreal sy)
QString toString(FormattingOptions options) const const
bool canConvert() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:17:55 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.