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/MapCSSParser>
12#include <KOSMIndoorMap/OverlaySource>
13
14#include <QDebug>
15#include <QFile>
16#include <QGuiApplication>
17#include <QPainter>
18#include <QPalette>
19#include <QQuickWindow>
20#include <QStandardPaths>
21#include <QTimeZone>
22
23using namespace KOSMIndoorMap;
24
25MapItem::MapItem(QQuickItem *parent)
26 : QQuickPaintedItem(parent)
27 , m_loader(new MapLoader(this))
28 , m_view(new View(this))
29 , m_floorLevelModel(new FloorLevelModel(this))
30{
31 connect(m_loader, &MapLoader::isLoadingChanged, this, &MapItem::clear);
32 connect(m_loader, &MapLoader::done, this, &MapItem::loaderDone);
33
34 m_view->setScreenSize({100, 100}); // FIXME this breaks view when done too late!
35 m_controller.setView(m_view);
36 connect(m_view, &View::floorLevelChanged, this, [this]() { update(); });
37 connect(m_view, &View::transformationChanged, this, [this]() { update(); });
38
39 setStylesheetName({}); // set default stylesheet
40}
41
42MapItem::~MapItem() = default;
43
44void MapItem::paint(QPainter *painter)
45{
46 m_controller.updateScene(m_sg);
47 m_renderer.setPainter(painter);
48 m_renderer.render(m_sg, m_view);
49}
50
51MapLoader* MapItem::loader() const
52{
53 return m_loader;
54}
55
56View* MapItem::view() const
57{
58 return m_view;
59}
60
61QString MapItem::styleSheetName() const
62{
63 return m_styleSheetName;
64}
65
66void MapItem::setStylesheetName(const QString &styleSheet)
67{
68 QString styleFile;
69
70 if (styleSheet.isEmpty() || styleSheet == QLatin1String("default")) {
71 if (QGuiApplication::palette().base().color().value() > 128) {
72 setStylesheetName(QStringLiteral("breeze-light"));
73 } else {
74 setStylesheetName(QStringLiteral("breeze-dark"));
75 }
76 return;
77 } else {
78 styleFile = styleSheet.contains(QLatin1Char(':')) ? QUrl::fromUserInput(styleSheet).toLocalFile() : styleSheet;
79 if (!QFile::exists(styleFile)) {
80#ifndef Q_OS_ANDROID
82#else
84#endif
85 searchPaths.push_back(QStringLiteral(":"));
86 for (const auto &searchPath : std::as_const(searchPaths)) {
87 const QString f = searchPath + QLatin1String("/org.kde.kosmindoormap/assets/css/") + styleSheet + QLatin1String(".mapcss");
88 if (QFile::exists(f)) {
89 qDebug() << "resolved stylesheet name to" << f;
90 styleFile = f;
91 break;
92 }
93 }
94 }
95 }
96
97 if (m_styleSheetName == styleFile) {
98 return;
99 }
100 m_styleSheetName = styleFile;
101 m_style = MapCSSStyle();
102
103 if (!m_styleSheetName.isEmpty()) {
104 MapCSSParser cssParser;
105 m_style = cssParser.parse(m_styleSheetName);
106
107 if (cssParser.hasError()) {
108 m_errorMessage = cssParser.errorMessage();
109 Q_EMIT errorChanged();
110 return;
111 }
112 m_errorMessage.clear();
113 Q_EMIT errorChanged();
114 }
115
116 m_style.compile(m_data.dataSet());
117 m_controller.setStyleSheet(&m_style);
118
119 Q_EMIT styleSheetChanged();
120 update();
121}
122
123FloorLevelModel* MapItem::floorLevelModel() const
124{
125 return m_floorLevelModel;
126}
127
128void MapItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
129{
130 QQuickPaintedItem::geometryChange(newGeometry, oldGeometry);
131 m_view->setScreenSize(newGeometry.size().toSize());
132 // the scale factor isn't automatically applied to the paint device, only to the input coordinates
133 // so we need to handle this manually here
134 if (window()) {
135 m_view->setDeviceTransform(QTransform::fromScale(window()->devicePixelRatio(), window()->devicePixelRatio()));
136 }
137}
138
139void MapItem::loaderDone()
140{
141 m_floorLevelModel->setMapData(nullptr);
142 m_sg.clear();
143
144 if (!m_loader->hasError()) {
145 auto data = m_loader->takeData();
146 if (data.regionCode().isEmpty()) {
147 data.setRegionCode(m_data.regionCode());
148 }
149 data.setTimeZone(m_data.timeZone());
150 m_data = std::move(data);
151 m_view->setSceneBoundingBox(m_data.boundingBox());
152 m_controller.setMapData(m_data);
153 m_style.compile(m_data.dataSet());
154 m_controller.setStyleSheet(&m_style);
155 m_view->setLevel(0);
156 m_floorLevelModel->setMapData(&m_data);
157 m_view->floorLevelChanged();
158 Q_EMIT mapDataChanged();
159 }
160
161 Q_EMIT errorChanged();
162 update();
163}
164
165OSMElement MapItem::elementAt(double x, double y) const
166{
167 HitDetector detector;
168 const auto item = detector.itemAt(QPointF(x, y), m_sg, m_view);
169 if (item) {
170 qDebug() << item->element.url();
171 for (auto it = item->element.tagsBegin(); it != item->element.tagsEnd(); ++it) {
172 qDebug() << " " << (*it).key.name() << (*it).value;
173 }
174 return OSMElement(item->element);
175 }
176 return {};
177}
178
179void MapItem::clear()
180{
181 if (!m_loader->isLoading() || m_sg.items().empty()) {
182 return;
183 }
184
185 m_sg.clear();
186 m_data = MapData();
187 m_controller.setMapData(m_data);
188 Q_EMIT mapDataChanged();
189 Q_EMIT errorChanged();
190 update();
191}
192
193bool MapItem::hasError() const
194{
195 return !m_errorMessage.isEmpty() || m_loader->hasError();
196}
197
199{
200 return m_errorMessage.isEmpty() ? m_loader->errorMessage() : m_errorMessage;
201}
202
203MapData MapItem::mapData() const
204{
205 return m_data;
206}
207
209{
210 return m_overlaySources;
211}
212
213void MapItem::setOverlaySources(const QVariant &overlays)
214{
215 const auto oldOwnedOverlays = std::move(m_ownedOverlaySources);
216
217 std::vector<QPointer<AbstractOverlaySource>> sources;
218 if (overlays.canConvert<QVariantList>()) {
219 const auto l = overlays.value<QVariantList>();
220 for (const auto &v : l) {
221 addOverlaySource(sources, v);
222 }
223 } else {
224 addOverlaySource(sources, overlays);
225 }
226
227 for (const auto &overlay : sources) {
228 connect(overlay.data(), &AbstractOverlaySource::update, this, &MapItem::overlayUpdate, Qt::UniqueConnection);
229 connect(overlay.data(), &AbstractOverlaySource::reset, this, &MapItem::overlayReset, Qt::UniqueConnection);
230 }
231
232 m_controller.setOverlaySources(std::move(sources));
233 Q_EMIT overlaySourcesChanged();
234 update();
235}
236
237void MapItem::addOverlaySource(std::vector<QPointer<AbstractOverlaySource>> &overlaySources, const QVariant &source)
238{
239 const auto obj = source.value<QObject*>();
240 if (auto model = qobject_cast<QAbstractItemModel*>(obj)) {
241 auto overlay = std::make_unique<ModelOverlaySource>(model);
242 overlaySources.push_back(overlay.get());
243 m_ownedOverlaySources.push_back(std::move(overlay));
244 } else if (auto source = qobject_cast<AbstractOverlaySource*>(obj)) {
245 overlaySources.push_back(source);
246 } else {
247 qWarning() << "unsupported overlay source:" << source << obj;
248 }
249}
250
251void MapItem::overlayUpdate()
252{
253 m_controller.overlaySourceUpdated();
254 update();
255}
256
257void MapItem::overlayReset()
258{
259 m_style.compile(m_data.dataSet());
260}
261
263{
264 return m_data.regionCode();
265}
266
267void MapItem::setRegion(const QString &region)
268{
269 if (m_data.regionCode() == region) {
270 return;
271 }
272
273 m_data.setRegionCode(region);
274 Q_EMIT regionChanged();
275}
276
277QString MapItem::timeZoneId() const
278{
279 return QString::fromUtf8(m_data.timeZone().id());
280}
281
282void MapItem::setTimeZoneId(const QString &tz)
283{
284 const auto tzId = tz.toUtf8();
285 if (m_data.timeZone().id() == tzId) {
286 return;
287 }
288
289 m_data.setTimeZone(QTimeZone(tzId));
290 Q_EMIT timeZoneChanged();
291}
292
294{
295 return OSMElement(m_controller.hoveredElement());
296}
297
298void MapItem::setHoveredElement(const OSMElement &element)
299{
300 if (m_controller.hoveredElement() == element.element()) {
301 return;
302 }
303 m_controller.setHoveredElement(element.element());
304 Q_EMIT hoveredElementChanged();
305 update();
306}
307
308#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.
A parsed MapCSS style sheet.
Definition mapcssstyle.h:33
void compile(const 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:39
KOSMIndoorMap::OSMElement hoveredElement
Currently hovered element.
Definition mapitem.h:59
QVariant overlaySources
Sources for overlays that should be rendered on top of the map.
Definition mapitem.h:46
QString region
ISO 3166-1/2 country or region code this map area is in.
Definition mapitem.h:51
QString errorMessage
Details on the error.
Definition mapitem.h:41
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:19
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.
OSM::Element element
The OSM::Element this item refers to.
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.
bool exists() const const
QPalette palette()
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
void update()
QQuickWindow * window() const const
QSizeF size() const const
QSize toSize() const const
QStringList standardLocations(StandardLocation type)
void clear()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
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)
QUrl fromUserInput(const QString &userInput, const QString &workingDirectory, UserInputResolutionOptions options)
QString toLocalFile() 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 Tue Mar 26 2024 11:20:03 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.