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 Qt::Literals::StringLiterals;
34using namespace KOSM;
35
36// https://github.com/openstreetmap/iD/blob/develop/API.md
37static void openElementInId(OSM::Element element)
38{
39 QUrl url;
40 url.setScheme(QStringLiteral("https"));
41 url.setHost(QStringLiteral("www.openstreetmap.org"));
42 url.setPath(QStringLiteral("/edit"));
43
45 query.addQueryItem(QStringLiteral("editor"), QStringLiteral("id"));
46 query.addQueryItem(QLatin1StringView(OSM::typeName(element.type())), QString::number(element.id()));
47
48 url.setQuery(query);
49 qCDebug(EditorLog) << url;
51}
52
53static void openBoundBoxInId(OSM::BoundingBox box)
54{
55 QUrl url;
56 url.setScheme(QStringLiteral("https"));
57 url.setHost(QStringLiteral("www.openstreetmap.org"));
58 url.setPath(QStringLiteral("/edit"));
59
61 query.addQueryItem(QStringLiteral("editor"), QStringLiteral("id"));
62 query.addQueryItem(QStringLiteral("lat"), QString::number(box.center().latF()));
63 query.addQueryItem(QStringLiteral("lon"), QString::number(box.center().lonF()));
64 query.addQueryItem(QStringLiteral("zoom"), QStringLiteral("17")); // TODO compute zoom based on box size
65
66 url.setQuery(query);
68}
69
70static QUrl makeJosmLoadAndZoomCommand(OSM::BoundingBox box, OSM::Element element)
71{
72 QUrl url;
73 url.setPath(QStringLiteral("/load_and_zoom"));
74
76 // ensure bbox is not 0x0 for nodes
77 query.addQueryItem(QStringLiteral("left"), QString::number(box.min.lonF() - 0.0001));
78 query.addQueryItem(QStringLiteral("bottom"), QString::number(box.min.latF() - 0.0001));
79 query.addQueryItem(QStringLiteral("right"), QString::number(box.max.lonF() + 0.0001));
80 query.addQueryItem(QStringLiteral("top"), QString::number(box.max.latF() + 0.0001));
81 query.addQueryItem(QStringLiteral("select"), QLatin1StringView(OSM::typeName(element.type())) + QString::number(element.id()));
82
83 url.setQuery(query);
84 return url;
85}
86
87#ifdef Q_OS_ANDROID
88// https://vespucci.io/tutorials/vespucci_intents/
89static void openVespucci(OSM::BoundingBox box, OSM::Element element = {})
90{
91 auto url = makeJosmLoadAndZoomCommand(box, element);
92 url.setScheme(QStringLiteral("josm"));
93 qCDebug(EditorLog) << url;
95}
96
97#else
98
99static std::unique_ptr<QNetworkAccessManager> s_nam;
100
101static void josmRemoteCommand(const QUrl &url, QElapsedTimer timeout)
102{
103 if (!s_nam) {
104 s_nam = std::make_unique<QNetworkAccessManager>();
105 }
106 auto reply = s_nam->get(QNetworkRequest(url));
107 QObject::connect(reply, &QNetworkReply::finished, QCoreApplication::instance(), [reply, url, timeout]() {
108 reply->deleteLater();
109 qCDebug(EditorLog) << reply->errorString();
110 qCDebug(EditorLog) << reply->readAll();
111 // retry in case JOSM is still starting up
112 if (reply->error() != QNetworkReply::NoError && timeout.elapsed() < 30000) {
113 QTimer::singleShot(1000, QCoreApplication::instance(), [url, timeout]() { josmRemoteCommand(url, timeout); });
114 }
115 });
116}
117
118// https://josm.openstreetmap.de/wiki/Help/RemoteControlCommands
119static void openJosm(OSM::BoundingBox box, OSM::Element element = {})
120{
121#if HAVE_KSERVICE
122 QTcpSocket socket;
124 if (!socket.waitForConnected(100)) {
125 qCDebug(EditorLog) << "JOSM not running yet, or doesn't have remote control enabled." << socket.errorString();
126 auto s = KService::serviceByDesktopName(QStringLiteral("org.openstreetmap.josm"));
127 qCDebug(EditorLog) << "JOSM not running yet, or doesn't have remote control enabled." << s->exec();
128 Q_ASSERT(s);
129 auto args = KShell::splitArgs(s->exec());
130 if (args.isEmpty()) {
131 return;
132 }
133 const auto program = args.takeFirst();
134 QProcess::startDetached(program, args);
135 }
136 socket.close();
137#endif
138
139 auto url = makeJosmLoadAndZoomCommand(box, element);
140 url.setScheme(QStringLiteral("http"));
141 url.setHost(QStringLiteral("127.0.0.1"));
142 url.setPort(8111);
143 qCDebug(EditorLog) << url;
144
145 QElapsedTimer timeout;
146 timeout.start();
147 josmRemoteCommand(url, timeout);
148}
149#endif
150
151bool EditorController::hasEditor(Editor editor)
152{
153 switch (editor) {
154 case ID:
155 return true;
156 case JOSM:
157#if HAVE_KSERVICE
158 {
159 auto s = KService::serviceByDesktopName(QStringLiteral("org.openstreetmap.josm"));
160 return s;
161 }
162#else
163 return false;
164#endif
165 case Vespucci:
166#ifdef Q_OS_ANDROID
167 return QJniObject::callStaticMethod<jboolean>("org.kde.osm.editorcontroller.EditorController",
168 "hasVespucci", "(Landroid/content/Context;)Z", QNativeInterface::QAndroidApplication::context());
169#else
170 return false;
171#endif
172 }
173
174 return false;
175}
176
177void EditorController::editElement(OSM::Element element, Editor editor)
178{
179 if (element.type() == OSM::Type::Null) {
180 return;
181 }
182
183 qCDebug(EditorLog) << element.url() << editor;
184 switch (editor) {
185 case ID:
186 openElementInId(element);
187 break;
188 case JOSM:
189#ifndef Q_OS_ANDROID
190 openJosm(element.boundingBox(), element);
191#endif
192 break;
193 case Vespucci:
194#ifdef Q_OS_ANDROID
195 openVespucci(element.boundingBox(), element);
196#endif
197 break;
198 }
199}
200
201void EditorController::editBoundingBox(OSM::BoundingBox box, Editor editor)
202{
203 qCDebug(EditorLog) << box << editor;
204 switch (editor) {
205 case ID:
206 openBoundBoxInId(box);
207 break;
208 case JOSM:
209#ifndef Q_OS_ANDROID
210 openJosm(box);
211#endif
212 break;
213 case Vespucci:
214#ifdef Q_OS_ANDROID
215 openVespucci(box);
216#endif
217 break;
218 }
219}
220
221#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)
KOSM_EXPORT const char * typeName(Type type)
Element type name.
Definition datatypes.cpp:11
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)
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 Fri Nov 29 2024 11:55:43 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.