diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 28e23c4ba..000d51062 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,6 +58,7 @@ add_library(neochat STATIC delegatesizehelper.cpp models/livelocationsmodel.cpp models/locationsmodel.cpp + locationhelper.cpp ) ecm_qt_declare_logging_category(neochat diff --git a/src/locationhelper.cpp b/src/locationhelper.cpp new file mode 100644 index 000000000..aa8679c5d --- /dev/null +++ b/src/locationhelper.cpp @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2023 Volker Krause +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "locationhelper.h" + +#include + +QRectF LocationHelper::unite(const QRectF &r1, const QRectF &r2) +{ + // this looks weird but is actually intentional as we need to handle point-like "rects" as well + if ((!r1.isEmpty() || r1.isNull()) && (!r2.isEmpty() || r2.isNull())) { + return r1 | r2; + } + return (!r1.isEmpty() || r1.isNull()) ? r1 : r2; +} + +QPointF LocationHelper::center(const QRectF &r) +{ + return r.center(); +} + +constexpr inline double degToRad(double deg) +{ + return deg / 180.0 * M_PI; +} + +static QPointF mercatorProject(double lat, double lon, double zoom) +{ + const auto x = (256.0 / (2.0 * M_PI)) * std::pow(2.0, zoom) * (degToRad(lon) + M_PI); + const auto y = (256.0 / (2.0 * M_PI)) * std::pow(2.0, zoom) * (M_PI - std::log(std::tan(M_PI / 4.0 + degToRad(lat) / 2.0))); + return QPointF(x, y); +} + +float LocationHelper::zoomToFit(const QRectF &r, float mapWidth, float mapHeight) +{ + const auto p1 = mercatorProject(r.bottomLeft().y(), r.bottomLeft().x(), 1.0); + const auto p2 = mercatorProject(r.topRight().y(), r.topRight().x(), 1.0); + + const auto zx = std::log2((mapWidth / (p2.x() - p1.x()))); + const auto zy = std::log2((mapHeight / (p2.y() - p1.y()))); + const auto z = std::min(zx, zy); + + return std::clamp(z, 5.0, 18.0); +} diff --git a/src/locationhelper.h b/src/locationhelper.h new file mode 100644 index 000000000..04a8777fc --- /dev/null +++ b/src/locationhelper.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Volker Krause +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "linkpreviewer.h" +#include +#include + +/** Location related helper functions for QML. */ +class LocationHelper +{ + Q_GADGET +public: + /** Unite two rectanlges. */ + Q_INVOKABLE static QRectF unite(const QRectF &r1, const QRectF &r2); + /** Returns the center of @p r. */ + Q_INVOKABLE static QPointF center(const QRectF &r); + + /** Returns the highest zoom level to fit @r into a map of size @p mapWidth x @p mapHeight. */ + Q_INVOKABLE static float zoomToFit(const QRectF &r, float mapWidth, float mapHeight); +}; + +Q_DECLARE_METATYPE(LocationHelper) diff --git a/src/main.cpp b/src/main.cpp index 0fb4a70d9..a1b9edc73 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,6 +46,7 @@ #include "delegatesizehelper.h" #include "filetypesingleton.h" #include "linkpreviewer.h" +#include "locationhelper.h" #include "logger.h" #include "login.h" #include "matriximageprovider.h" @@ -288,6 +289,9 @@ int main(int argc, char *argv[]) return engine->toScriptValue(KAboutData::applicationData()); }); qmlRegisterSingletonType(QUrl("qrc:/OsmLocationPlugin.qml"), "org.kde.neochat", 1, 0, "OsmLocationPlugin"); + qmlRegisterSingletonType("org.kde.neochat", 1, 0, "LocationHelper", [](QQmlEngine *engine, QJSEngine *) -> QJSValue { + return engine->toScriptValue(LocationHelper()); + }); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) qRegisterMetaTypeStreamOperators(); diff --git a/src/qml/Component/LocationPage.qml b/src/qml/Component/LocationPage.qml index 627a2b490..d784dbeaf 100644 --- a/src/qml/Component/LocationPage.qml +++ b/src/qml/Component/LocationPage.qml @@ -23,8 +23,15 @@ Kirigami.Page { anchors.fill: parent plugin: OsmLocationPlugin.plugin + center: { + let c = LocationHelper.center(LocationHelper.unite(locationsModel.boundingBox, liveLocationsModel.boundingBox)); + return QtPositioning.coordinate(c.y, c.x); + } + zoomLevel: LocationHelper.zoomToFit(LocationHelper.unite(locationsModel.boundingBox, liveLocationsModel.boundingBox), map.width, map.height) + MapItemView { model: LocationsModel { + id: locationsModel room: locationsPage.room } delegate: LocationMapItem { @@ -34,6 +41,7 @@ Kirigami.Page { MapItemView { model: LiveLocationsModel { + id: liveLocationsModel room: locationsPage.room } delegate: LocationMapItem {}