KOSMIndoorMap

editorcontroller.cpp
1/*
2 SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include <config-editorcontroller.h>
7
8#include "editorcontroller.h"
9#include "logging.h"
10
11#if HAVE_KSERVICE
12#include <KService>
13#include <KShell>
14#endif
15
16#ifdef Q_OS_ANDROID
17#include <QJniObject>
18#endif
19
20#include <QCoreApplication>
21#include <QDesktopServices>
22#include <QElapsedTimer>
23#include <QHostAddress>
24#include <QNetworkAccessManager>
25#include <QNetworkReply>
26#include <QNetworkRequest>
27#include <QProcess>
28#include <QTcpSocket>
29#include <QTimer>
30#include <QUrl>
31#include <QUrlQuery>
32
33using namespace KOSM;
34
35// https://github.com/openstreetmap/iD/blob/develop/API.md
36static void openElementInId(OSM::Element element)
37{
38 QUrl url;
39 url.setScheme(QStringLiteral("https"));
40 url.setHost(QStringLiteral("www.openstreetmap.org"));
41 url.setPath(QStringLiteral("/edit"));
42
44 query.addQueryItem(QStringLiteral("editor"), QStringLiteral("id"));
45 switch (element.type()) {
46 case OSM::Type::Null:
47 Q_UNREACHABLE();
48 case OSM::Type::Node:
49 query.addQueryItem(QStringLiteral("node"), QString::number(element.id()));
50 break;
51 case OSM::Type::Way:
52 query.addQueryItem(QStringLiteral("way"), QString::number(element.id()));
53 break;
54 case OSM::Type::Relation:
55 query.addQueryItem(QStringLiteral("relation"), QString::number(element.id()));
56 break;
57 }
58
59 url.setQuery(query);
60 qCDebug(EditorLog) << url;
62}
63
64static void openBoundBoxInId(OSM::BoundingBox box)
65{
66 QUrl url;
67 url.setScheme(QStringLiteral("https"));
68 url.setHost(QStringLiteral("www.openstreetmap.org"));
69 url.setPath(QStringLiteral("/edit"));
70
72 query.addQueryItem(QStringLiteral("editor"), QStringLiteral("id"));
73 query.addQueryItem(QStringLiteral("lat"), QString::number(box.center().latF()));
74 query.addQueryItem(QStringLiteral("lon"), QString::number(box.center().lonF()));
75 query.addQueryItem(QStringLiteral("zoom"), QStringLiteral("17")); // TODO compute zoom based on box size
76
77 url.setQuery(query);
79}
80
81static QUrl makeJosmLoadAndZoomCommand(OSM::BoundingBox box, OSM::Element element)
82{
83 QUrl url;
84 url.setPath(QStringLiteral("/load_and_zoom"));
85
87 // ensure bbox is not 0x0 for nodes
88 query.addQueryItem(QStringLiteral("left"), QString::number(box.min.lonF() - 0.0001));
89 query.addQueryItem(QStringLiteral("bottom"), QString::number(box.min.latF() - 0.0001));
90 query.addQueryItem(QStringLiteral("right"), QString::number(box.max.lonF() + 0.0001));
91 query.addQueryItem(QStringLiteral("top"), QString::number(box.max.latF() + 0.0001));
92
93 switch (element.type()) {
94 case OSM::Type::Null:
95 break;
96 case OSM::Type::Node:
97 query.addQueryItem(QStringLiteral("select"), QLatin1String("node") + QString::number(element.id()));
98 break;
99 case OSM::Type::Way:
100 query.addQueryItem(QStringLiteral("select"), QLatin1String("way") + QString::number(element.id()));
101 break;
102 case OSM::Type::Relation:
103 query.addQueryItem(QStringLiteral("select"), QLatin1String("relation") + QString::number(element.id()));
104 break;
105 }
106
107 url.setQuery(query);
108 return url;
109}
110
111#ifdef Q_OS_ANDROID
112// https://vespucci.io/tutorials/vespucci_intents/
113static void openVespucci(OSM::BoundingBox box, OSM::Element element = {})
114{
115 auto url = makeJosmLoadAndZoomCommand(box, element);
116 url.setScheme(QStringLiteral("josm"));
117 qCDebug(EditorLog) << url;
119}
120
121#else
122
123static std::unique_ptr<QNetworkAccessManager> s_nam;
124
125static void josmRemoteCommand(const QUrl &url, QElapsedTimer timeout)
126{
127 if (!s_nam) {
128 s_nam = std::make_unique<QNetworkAccessManager>();
129 }
130 auto reply = s_nam->get(QNetworkRequest(url));
131 QObject::connect(reply, &QNetworkReply::finished, QCoreApplication::instance(), [reply, url, timeout]() {
132 reply->deleteLater();
133 qCDebug(EditorLog) << reply->errorString();
134 qCDebug(EditorLog) << reply->readAll();
135 // retry in case JOSM is still starting up
136 if (reply->error() != QNetworkReply::NoError && timeout.elapsed() < 30000) {
137 QTimer::singleShot(1000, QCoreApplication::instance(), [url, timeout]() { josmRemoteCommand(url, timeout); });
138 }
139 });
140}
141
142// https://josm.openstreetmap.de/wiki/Help/RemoteControlCommands
143static void openJosm(OSM::BoundingBox box, OSM::Element element = {})
144{
145#if HAVE_KSERVICE
146 QTcpSocket socket;
148 if (!socket.waitForConnected(100)) {
149 qCDebug(EditorLog) << "JOSM not running yet, or doesn't have remote control enabled." << socket.errorString();
150 auto s = KService::serviceByDesktopName(QStringLiteral("org.openstreetmap.josm"));
151 qCDebug(EditorLog) << "JOSM not running yet, or doesn't have remote control enabled." << s->exec();
152 Q_ASSERT(s);
153 auto args = KShell::splitArgs(s->exec());
154 if (args.isEmpty()) {
155 return;
156 }
157 const auto program = args.takeFirst();
158 QProcess::startDetached(program, args);
159 }
160 socket.close();
161#endif
162
163 auto url = makeJosmLoadAndZoomCommand(box, element);
164 url.setScheme(QStringLiteral("http"));
165 url.setHost(QStringLiteral("127.0.0.1"));
166 url.setPort(8111);
167 qCDebug(EditorLog) << url;
168
169 QElapsedTimer timeout;
170 timeout.start();
171 josmRemoteCommand(url, timeout);
172}
173#endif
174
175bool EditorController::hasEditor(Editor editor)
176{
177 switch (editor) {
178 case ID:
179 return true;
180 case JOSM:
181#if HAVE_KSERVICE
182 {
183 auto s = KService::serviceByDesktopName(QStringLiteral("org.openstreetmap.josm"));
184 return s;
185 }
186#else
187 return false;
188#endif
189 case Vespucci:
190#ifdef Q_OS_ANDROID
191 return QJniObject::callStaticMethod<jboolean>("org.kde.osm.editorcontroller.EditorController",
192 "hasVespucci", "(Landroid/content/Context;)Z", QNativeInterface::QAndroidApplication::context());
193#else
194 return false;
195#endif
196 }
197
198 return false;
199}
200
201void EditorController::editElement(OSM::Element element, Editor editor)
202{
203 if (element.type() == OSM::Type::Null) {
204 return;
205 }
206
207 qCDebug(EditorLog) << element.url() << editor;
208 switch (editor) {
209 case ID:
210 openElementInId(element);
211 break;
212 case JOSM:
213#ifndef Q_OS_ANDROID
214 openJosm(element.boundingBox(), element);
215#endif
216 break;
217 case Vespucci:
218#ifdef Q_OS_ANDROID
219 openVespucci(element.boundingBox(), element);
220#endif
221 break;
222 }
223}
224
225void EditorController::editBoundingBox(OSM::BoundingBox box, Editor editor)
226{
227 qCDebug(EditorLog) << box << editor;
228 switch (editor) {
229 case ID:
230 openBoundBoxInId(box);
231 break;
232 case JOSM:
233#ifndef Q_OS_ANDROID
234 openJosm(box);
235#endif
236 break;
237 case Vespucci:
238#ifdef Q_OS_ANDROID
239 openVespucci(box);
240#endif
241 break;
242 }
243}
244
245#include "moc_editorcontroller.cpp"
static Ptr serviceByDesktopName(const QString &_name)
Bounding box, ie.
Definition datatypes.h:95
A reference to any of OSM::Node/OSM::Way/OSM::Relation.
Definition element.h:24
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
KCOREADDONS_EXPORT QStringList splitArgs(const QString &cmd, Options flags=NoOptions, Errors *err=nullptr)
virtual void close() override
void connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode)
virtual bool waitForConnected(int msecs)
QCoreApplication * instance()
bool openUrl(const QUrl &url)
qint64 elapsed() const const
QString errorString() const const
auto callStaticMethod(const char *className, const char *methodName, Args &&... args)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid)
QString number(double n, char format, int precision)
void setHost(const QString &host, ParsingMode mode)
void setPath(const QString &path, ParsingMode mode)
void setPort(int port)
void setQuery(const QString &query, ParsingMode mode)
void setScheme(const QString &scheme)
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.