1 /*
2     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include <KOSMIndoorMap/FloorLevelModel>
8 #include <KOSMIndoorMap/HitDetector>
9 #include <KOSMIndoorMap/MapCSSParser>
10 #include <KOSMIndoorMap/MapCSSStyle>
11 #include <KOSMIndoorMap/MapData>
12 #include <KOSMIndoorMap/MapLoader>
13 #include <KOSMIndoorMap/PainterRenderer>
14 #include <KOSMIndoorMap/SceneController>
15 #include <KOSMIndoorMap/SceneGraph>
16 #include <KOSMIndoorMap/View>
17 
18 #include <QApplication>
19 #include <QCommandLineParser>
20 #include <QMouseEvent>
21 #include <QComboBox>
22 #include <QHBoxLayout>
23 #include <QPainter>
24 #include <QRegularExpression>
25 
26 using namespace KOSMIndoorMap;
27 
cssPath(const QString & styleName)28 static QString cssPath(const QString &styleName)
29 {
30     return QLatin1String(SOURCE_DIR "/../src/map/assets/css/") + styleName + QLatin1String(".mapcss");
31 //     return QLatin1String(":/org.kde.kosmindoormap/assets/css/") + styleName + QLatin1String(".mapcss");
32 }
33 
34 class MapWidget : public QWidget
35 {
36 public:
37     explicit MapWidget(QWidget *parent = nullptr);
38     void paintEvent(QPaintEvent *event) override;
39     void resizeEvent(QResizeEvent *event) override;
40     void mousePressEvent(QMouseEvent *event) override;
41     void mouseMoveEvent(QMouseEvent *event) override;
42     void mouseReleaseEvent(QMouseEvent *event) override;
43     void wheelEvent(QWheelEvent *event) override;
44     void setMapData(MapData &&data);
45     void setStyleSheet(const QString &styleName);
46 
47     MapData m_data;
48     SceneGraph m_sg;
49     MapCSSStyle m_style;
50     SceneController m_controller;
51     PainterRenderer m_renderer;
52     View m_view;
53     QPoint m_lastPanPoint;
54 };
55 
MapWidget(QWidget * parent)56 MapWidget::MapWidget(QWidget* parent)
57     : QWidget(parent)
58 {
59     m_view.setScreenSize(size());
60     m_controller.setView(&m_view);
61 }
62 
paintEvent(QPaintEvent * event)63 void MapWidget::paintEvent(QPaintEvent *event)
64 {
65     m_controller.updateScene(m_sg);
66     QPainter p(this);
67     m_renderer.setPainter(&p);
68     m_renderer.render(m_sg, &m_view);
69     return QWidget::paintEvent(event);
70 }
71 
resizeEvent(QResizeEvent * event)72 void MapWidget::resizeEvent(QResizeEvent *event)
73 {
74     QWidget::resizeEvent(event);
75     m_view.setScreenSize(size());
76 }
77 
mousePressEvent(QMouseEvent * event)78 void MapWidget::mousePressEvent(QMouseEvent *event)
79 {
80     m_lastPanPoint = event->pos();
81     QWidget::mousePressEvent(event);
82 }
83 
mouseMoveEvent(QMouseEvent * event)84 void MapWidget::mouseMoveEvent(QMouseEvent *event)
85 {
86     m_view.panScreenSpace(m_lastPanPoint - event->pos());
87     m_lastPanPoint = event->pos();
88     QWidget::mouseMoveEvent(event);
89     update();
90 }
91 
mouseReleaseEvent(QMouseEvent * event)92 void MapWidget::mouseReleaseEvent(QMouseEvent *event)
93 {
94     if (event->button() == Qt::RightButton) {
95         HitDetector detector;
96         const auto items = detector.itemsAt(event->pos(), m_sg, &m_view);
97         for (const auto item : items) {
98             qDebug() << item->element.url();
99             for (auto it = item->element.tagsBegin(); it != item->element.tagsEnd(); ++it) {
100                 qDebug() << "    " << (*it).key.name() << (*it).value;
101             }
102             switch (item->element.type()) {
103                 case OSM::Type::Null:
104                 case OSM::Type::Node:
105                     break;
106                 case OSM::Type::Way:
107                     for (const auto &node : item->element.way()->nodes) {
108                         qDebug() << "      " << node;
109                     }
110                     break;
111                 case OSM::Type::Relation:
112                     for (const auto &mem : item->element.relation()->members) {
113                         qDebug() << "      " << mem.role().name() << (int)mem.type() << mem.id;
114                     }
115                     break;
116             }
117         }
118     }
119 }
120 
wheelEvent(QWheelEvent * event)121 void MapWidget::wheelEvent(QWheelEvent *event)
122 {
123     if (event->angleDelta().y() > 0) {
124         m_view.zoomIn(event->position());
125     } else {
126         m_view.zoomOut(event->position());
127     }
128     QWidget::wheelEvent(event);
129     update();
130 }
131 
setMapData(MapData && data)132 void MapWidget::setMapData(MapData &&data)
133 {
134     m_data = std::move(data);
135     m_controller.setMapData(m_data);
136     m_view.setSceneBoundingBox(m_data.boundingBox());
137     m_style.compile(m_data.dataSet());
138     m_controller.setStyleSheet(&m_style);
139     update();
140 }
141 
setStyleSheet(const QString & styleName)142 void MapWidget::setStyleSheet(const QString &styleName)
143 {
144     MapCSSParser cssParser;
145     m_style = cssParser.parse(cssPath(styleName));
146     m_style.compile(m_data.dataSet());
147     m_controller.setStyleSheet(&m_style);
148 }
149 
150 
main(int argc,char ** argv)151 int main(int argc, char **argv)
152 {
153     QApplication app(argc, argv);
154     QCommandLineParser parser;
155     QCommandLineOption coordOpt({QStringLiteral("coordinate"), QStringLiteral("c")}, QStringLiteral("coordinate of the location to load"), QStringLiteral("lat,lon"));
156     parser.addOption(coordOpt);
157     QCommandLineOption fileOpt({QStringLiteral("file"), QStringLiteral("f")}, QStringLiteral("O5M or OSM PBF file to load"), QStringLiteral("file"));
158     parser.addOption(fileOpt);
159     parser.addHelpOption();
160     parser.addVersionOption();
161     parser.process(app);
162 
163     MapWidget widget;
164     widget.resize(480, 720);
165     widget.setStyleSheet(QStringLiteral("breeze-light"));
166 
167     auto layout = new QHBoxLayout(&widget);
168     layout->setAlignment(Qt::AlignTop);
169 
170     FloorLevelModel floorModel;
171     auto levelBox = new QComboBox;
172     levelBox->setModel(&floorModel);
173     layout->addWidget(levelBox);
174     QObject::connect(levelBox, &QComboBox::currentTextChanged, &app, [&]() {
175         widget.m_view.setLevel(levelBox->currentData().value<MapLevel>().numericLevel());
176         widget.update();
177     });
178 
179     auto styleBox = new QComboBox;
180     layout->addWidget(styleBox);
181     styleBox->addItems({QStringLiteral("breeze-light"), QStringLiteral("breeze-dark"), QStringLiteral("diagnostic")});
182     QObject::connect(styleBox, &QComboBox::currentTextChanged, &app, [&](const QString &styleName) {
183         widget.setStyleSheet(styleName);
184         widget.update();
185     });
186 
187     widget.show();
188 
189     MapLoader loader;
190     QObject::connect(&loader, &MapLoader::done, &app, [&]() {
191         widget.setMapData(loader.takeData());
192         floorModel.setMapData(&widget.m_data);
193         levelBox->setCurrentText(QLatin1String("0"));
194     });
195 
196     if (parser.isSet(fileOpt)) {
197         loader.loadFromFile(parser.value(fileOpt));
198     } else if (parser.isSet(coordOpt)) {
199         const auto s = parser.value(coordOpt).split(QRegularExpression(QStringLiteral("[,/;]")));
200         loader.loadForCoordinate(s.at(0).toDouble(), s.at(1).toDouble());
201     }
202 
203     return app.exec();
204 }
205