KOSMIndoorMap

mapitem.cpp
1 /*
2  SPDX-FileCopyrightText: 2020 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "mapitem.h"
8 
9 #include <KOSMIndoorMap/HitDetector>
10 #include <KOSMIndoorMap/MapCSSParser>
11 #include <KOSMIndoorMap/OverlaySource>
12 
13 #include <QDebug>
14 #include <QGuiApplication>
15 #include <QPainter>
16 #include <QPalette>
17 #include <QQuickWindow>
18 #include <QStandardPaths>
19 #include <QTimeZone>
20 
21 using namespace KOSMIndoorMap;
22 
23 MapItem::MapItem(QQuickItem *parent)
24  : QQuickPaintedItem(parent)
25  , m_loader(new MapLoader(this))
26  , m_view(new View(this))
27  , m_floorLevelModel(new FloorLevelModel(this))
28 {
29  connect(m_loader, &MapLoader::isLoadingChanged, this, &MapItem::clear);
30  connect(m_loader, &MapLoader::done, this, &MapItem::loaderDone);
31 
32  m_view->setScreenSize({100, 100}); // FIXME this breaks view when done too late!
33  m_controller.setView(m_view);
34  connect(m_view, &View::floorLevelChanged, this, [this]() { update(); });
35  connect(m_view, &View::transformationChanged, this, [this]() { update(); });
36 
37  setStylesheetName({}); // set default stylesheet
38 }
39 
40 MapItem::~MapItem() = default;
41 
42 void MapItem::paint(QPainter *painter)
43 {
44  m_controller.updateScene(m_sg);
45  m_renderer.setPainter(painter);
46  m_renderer.render(m_sg, m_view);
47 }
48 
49 MapLoader* MapItem::loader() const
50 {
51  return m_loader;
52 }
53 
54 View* MapItem::view() const
55 {
56  return m_view;
57 }
58 
59 QString MapItem::styleSheetName() const
60 {
61  return m_styleSheetName;
62 }
63 
64 void MapItem::setStylesheetName(const QString &styleSheet)
65 {
66  QString styleFile;
67 
68  if (styleSheet.isEmpty() || styleSheet == QLatin1String("default")) {
69  if (QGuiApplication::palette().base().color().value() > 128) {
70  setStylesheetName(QStringLiteral("breeze-light"));
71  } else {
72  setStylesheetName(QStringLiteral("breeze-dark"));
73  }
74  return;
75  } else {
76  styleFile = styleSheet.contains(QLatin1Char(':')) ? QUrl::fromUserInput(styleSheet).toLocalFile() : styleSheet;
77  if (!QFile::exists(styleFile)) {
78 #ifndef Q_OS_ANDROID
80 #else
82 #endif
83  searchPaths.push_back(QStringLiteral(":"));
84  for (const auto &searchPath : qAsConst(searchPaths)) {
85  const QString f = searchPath + QLatin1String("/org.kde.kosmindoormap/assets/css/") + styleSheet + QLatin1String(".mapcss");
86  if (QFile::exists(f)) {
87  qDebug() << "resolved stylesheet name to" << f;
88  styleFile = f;
89  break;
90  }
91  }
92  }
93  }
94 
95  if (m_styleSheetName == styleFile) {
96  return;
97  }
98  m_styleSheetName = styleFile;
99  m_style = MapCSSStyle();
100 
101  if (!m_styleSheetName.isEmpty()) {
102  MapCSSParser cssParser;
103  m_style = cssParser.parse(m_styleSheetName);
104 
105  if (cssParser.hasError()) {
106  m_errorMessage = cssParser.errorMessage();
107  Q_EMIT errorChanged();
108  return;
109  }
110  }
111 
112  m_style.compile(m_data.dataSet());
113  m_controller.setStyleSheet(&m_style);
114 
115  Q_EMIT styleSheetChanged();
116  update();
117 }
118 
119 FloorLevelModel* MapItem::floorLevelModel() const
120 {
121  return m_floorLevelModel;
122 }
123 
124 void MapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
125 {
126  QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry);
127  m_view->setScreenSize(newGeometry.size().toSize());
128  // the scale factor isn't automatically applied to the paint device, only to the input coordinates
129  // so we need to handle this manually here
130  m_view->setDeviceTransform(QTransform::fromScale(window()->devicePixelRatio(), window()->devicePixelRatio()));
131 }
132 
133 void MapItem::loaderDone()
134 {
135  m_floorLevelModel->setMapData(nullptr);
136  m_sg.clear();
137 
138  if (!m_loader->hasError()) {
139  auto data = m_loader->takeData();
140  if (data.regionCode().isEmpty()) {
141  data.setRegionCode(m_data.regionCode());
142  }
143  data.setTimeZone(m_data.timeZone());
144  m_data = std::move(data);
145  m_view->setSceneBoundingBox(m_data.boundingBox());
146  m_controller.setMapData(m_data);
147  m_style.compile(m_data.dataSet());
148  m_controller.setStyleSheet(&m_style);
149  m_view->setLevel(0);
150  m_floorLevelModel->setMapData(&m_data);
151  m_view->floorLevelChanged();
152  Q_EMIT mapDataChanged();
153  }
154 
155  Q_EMIT errorChanged();
156  update();
157 }
158 
159 OSMElement MapItem::elementAt(double x, double y) const
160 {
161  HitDetector detector;
162  const auto item = detector.itemAt(QPointF(x, y), m_sg, m_view);
163  if (item) {
164  qDebug() << item->element.url();
165  for (auto it = item->element.tagsBegin(); it != item->element.tagsEnd(); ++it) {
166  qDebug() << " " << (*it).key.name() << (*it).value;
167  }
168  return OSMElement(item->element);
169  }
170  return {};
171 }
172 
173 void MapItem::clear()
174 {
175  if (!m_loader->isLoading() || m_sg.items().empty()) {
176  return;
177  }
178 
179  m_sg.clear();
180  m_data = MapData();
181  m_controller.setMapData(m_data);
182  Q_EMIT mapDataChanged();
183  Q_EMIT errorChanged();
184  update();
185 }
186 
187 bool MapItem::hasError() const
188 {
189  return !m_errorMessage.isEmpty() || m_loader->hasError();
190 }
191 
192 QString MapItem::errorMessage() const
193 {
194  return m_errorMessage.isEmpty() ? m_loader->errorMessage() : m_errorMessage;
195 }
196 
197 MapData MapItem::mapData() const
198 {
199  return m_data;
200 }
201 
202 QVariant MapItem::overlaySources() const
203 {
204  return m_overlaySources;
205 }
206 
207 void MapItem::setOverlaySources(const QVariant &overlays)
208 {
209  const auto oldOwnedOverlays = std::move(m_ownedOverlaySources);
210 
211  std::vector<QPointer<AbstractOverlaySource>> sources;
212  if (overlays.canConvert<QVariantList>()) {
213  const auto l = overlays.value<QVariantList>();
214  for (const auto &v : l) {
215  addOverlaySource(sources, v);
216  }
217  } else {
218  addOverlaySource(sources, overlays);
219  }
220 
221  for (const auto &overlay : sources) {
222  connect(overlay.data(), &AbstractOverlaySource::update, this, &MapItem::overlayUpdate, Qt::UniqueConnection);
223  connect(overlay.data(), &AbstractOverlaySource::reset, this, &MapItem::overlayReset, Qt::UniqueConnection);
224  }
225 
226  m_controller.setOverlaySources(std::move(sources));
227  Q_EMIT overlaySourcesChanged();
228  update();
229 }
230 
231 void MapItem::addOverlaySource(std::vector<QPointer<AbstractOverlaySource>> &overlaySources, const QVariant &source)
232 {
233  const auto obj = source.value<QObject*>();
234  if (auto model = qobject_cast<QAbstractItemModel*>(obj)) {
235  auto overlay = std::make_unique<ModelOverlaySource>(model);
236  overlaySources.push_back(overlay.get());
237  m_ownedOverlaySources.push_back(std::move(overlay));
238  } else if (auto source = qobject_cast<AbstractOverlaySource*>(obj)) {
239  overlaySources.push_back(source);
240  } else {
241  qWarning() << "unsupported overlay source:" << source << obj;
242  }
243 }
244 
245 void MapItem::overlayUpdate()
246 {
247  m_controller.overlaySourceUpdated();
248  update();
249 }
250 
251 void MapItem::overlayReset()
252 {
253  m_style.compile(m_data.dataSet());
254 }
255 
256 QString MapItem::region() const
257 {
258  return m_data.regionCode();
259 }
260 
261 void MapItem::setRegion(const QString &region)
262 {
263  if (m_data.regionCode() == region) {
264  return;
265  }
266 
267  m_data.setRegionCode(region);
268  Q_EMIT regionChanged();
269 }
270 
271 QString MapItem::timeZoneId() const
272 {
273  return QString::fromUtf8(m_data.timeZone().id());
274 }
275 
276 void MapItem::setTimeZoneId(const QString &tz)
277 {
278  const auto tzId = tz.toUtf8();
279  if (m_data.timeZone().id() == tzId) {
280  return;
281  }
282 
283  m_data.setTimeZone(QTimeZone(tzId));
284  Q_EMIT timeZoneChanged();
285 }
const SceneGraphItem * itemAt(QPointF pos, const SceneGraph &sg, const View *view) const
Highest (in z-order) item at the given screen position.
Definition: hitdetector.cpp:18
bool canConvert(int targetTypeId) const const
OSM-based multi-floor indoor maps for buildings.
QTransform fromScale(qreal sx, qreal sy)
QML wrapper around an OSM element.
Definition: osmelement.h:18
Loader for OSM data for a single station or airport.
Definition: maploader.h:24
KJOBWIDGETS_EXPORT QWidget * window(KJob *job)
void reset()
Trigger style re-compilation.
A parsed MapCSS style sheet.
Definition: mapcssstyle.h:29
View transformations and transformation manipulation.
Definition: view.h:39
int value() const const
QSizeF size() const const
T value() const const
Picking hit detector.
Definition: hitdetector.h:29
bool exists() const const
virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
QUrl fromUserInput(const QString &userInput)
QStringList standardLocations(QStandardPaths::StandardLocation type)
void update()
Trigger map re-rendering when the source changes.
const QColor & color() const const
QString fromUtf8(const char *str, int size)
QPalette palette()
QSize toSize() const const
Raw OSM map data, separated by levels.
Definition: mapdata.h:59
bool isEmpty() const const
QString toLocalFile() const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
void done()
Emitted when the requested data has been loaded.
const QBrush & base() const const
void update(Part *part, const QByteArray &data, qint64 dataSize)
UniqueConnection
OSM::Element element
The OSM::Element this item refers to.
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Oct 23 2021 23:03:45 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.