KOSMIndoorMap

view.cpp
1/*
2 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "view.h"
8
9#include <osm/geomath.h>
10
11#include <cmath>
12
13using namespace KOSMIndoorMap;
14
15static constexpr const double SceneWorldSize = 256.0; // size of the scene when containing the full world
16static constexpr const double LatitudeLimit = 85.05112879806592; // invtan(sinh(pi)) + radToDeg
17static constexpr const auto MaxZoomFactor = 21; // 2^MaxZoomFactor subdivisions of the scene space
18
19View::View(QObject *parent)
20 : QObject(parent)
21{
22 setBeginTime(QDateTime::currentDateTime());
23}
24
25View::~View() = default;
26
28{
29 const auto lat = qBound(-LatitudeLimit, coord.latF(), LatitudeLimit);
30 return QPointF(
31 (coord.lonF() + 180.0) / 360.0 * SceneWorldSize,
32 SceneWorldSize / (2.0 * M_PI) * (M_PI - std::log(std::tan((M_PI / 4.0) + ((OSM::degToRad(lat) / 2.0)))))
33 );
34}
35
37{
38 const auto p1 = mapGeoToScene(box.min);
39 const auto p2 = mapGeoToScene(box.max);
40 return QRectF(QPointF(p1.x(), p2.y()), QPointF(p2.x(), p1.y()));
41}
42
44{
45 return OSM::Coordinate(
46 OSM::radToDeg(std::atan(std::sinh(M_PI * (1 - 2 * (p.y() / SceneWorldSize))))),
47 (p.x() / SceneWorldSize) * 360.0 - 180.0
48 );
49}
50
52{
53 const auto c1 = mapSceneToGeo(box.bottomLeft());
54 const auto c2 = mapSceneToGeo(box.topRight());
55 return OSM::BoundingBox(c1, c2);
56}
57
58int View::screenHeight() const
59{
60 return m_screenSize.height();
61}
62
64{
65 return m_screenSize.width();
66}
67
68void View::setScreenSize(QSize size)
69{
70 if (size.width() <= 0.0 || size.height() <= 0.0 || size == m_screenSize) {
71 return;
72 }
73
74 const auto dx = (double)size.width() / (double)screenWidth();
75 const auto dy = (double)size.height() / (double)screenHeight();
76 m_screenSize = size;
77
78 m_viewport.setWidth(m_viewport.width() * dx);
79 m_viewport.setHeight(m_viewport.height() * dy);
80 constrainViewToScene();
81 updateViewport();
82}
83
84int View::level() const
85{
86 return m_level;
87}
88
89void View::setLevel(int level)
90{
91 if (m_level == level) {
92 return;
93 }
94
95 m_level = level;
96 Q_EMIT floorLevelChanged();
97}
98
99double View::zoomLevel() const
100{
101 const auto dx = m_viewport.width() / (screenWidth() / SceneWorldSize) / 360.0;
102 return - std::log2(dx);
103}
104
105void View::setZoomLevel(double zoom, QPointF screenCenter)
106{
107 m_viewport = viewportForZoom(zoom, screenCenter);
108 updateViewport();
109}
110
111QRectF View::viewport() const
112{
113 return m_viewport;
114}
115
116void View::setViewport(const QRectF &viewport)
117{
118 m_viewport = viewport;
119 constrainViewToScene();
120 updateViewport();
121}
122
123QRectF View::viewportForZoom(double zoom, QPointF screenCenter) const
124{
125 auto z = std::pow(2.0, - std::min(zoom, (double)MaxZoomFactor));
126 const auto dx = ((screenWidth() / SceneWorldSize) * 360.0 * z) - m_viewport.width();
127 const auto dy = ((screenHeight() / SceneWorldSize) * 360.0 * z) - m_viewport.height();
128
129 const auto centerScene = mapScreenToScene(screenCenter);
130 if (!m_viewport.contains(centerScene)) {
131 return m_viewport; // invalid input
132 }
133
134 const auto xr = (centerScene.x() - m_viewport.x()) / m_viewport.width();
135 const auto yr = (centerScene.y() - m_viewport.y()) / m_viewport.height();
136
137 QRectF viewport(m_viewport);
138 viewport.adjust(-xr * dx, -yr * dy, (1-xr) * dx, (1-yr) * dy);
139 return constrainedViewport(viewport);
140}
141
143{
144 return m_bbox;
145}
146
147void View::setSceneBoundingBox(OSM::BoundingBox bbox)
148{
149 setSceneBoundingBox(mapGeoToScene(bbox));
150}
151
152void View::setSceneBoundingBox(const QRectF &bbox)
153{
154 if (m_bbox == bbox) {
155 return;
156 }
157 m_bbox = bbox;
158
159 // scale to fit horizontally
160 m_viewport = bbox;
161 const auto screenAspectRatio = (double)screenWidth() / (double)screenHeight();
162 m_viewport.setHeight(m_viewport.width() / screenAspectRatio);
163
164 // if necessary, scale to fit vertically
165 if (m_viewport.height() > m_bbox.height()) {
166 const auto dy = (double)m_bbox.height() / (double)m_viewport.height();
167 m_viewport.setHeight(m_viewport.height() * dy);
168 m_viewport.setWidth(m_viewport.width() * dy);
169 }
170
171 updateViewport();
172}
173
174
176{
177 return m_sceneToScreenTransform.map(scenePos);
178}
179
180QRectF View::mapSceneToScreen(const QRectF &sceneRect) const
181{
182 return QRectF(mapSceneToScreen(sceneRect.topLeft()), mapSceneToScreen(sceneRect.bottomRight()));
183}
184
186{
187 // TODO this can be implemented more efficiently
188 return m_screenToSceneTransform.map(screenPos);
189}
190
191double View::mapScreenDistanceToSceneDistance(double distance) const
192{
193 const auto p1 = mapScreenToScene(m_viewport.center());
194 const auto p2 = mapScreenToScene(m_viewport.center() + QPointF(1.0, 0));
195 // ### does not consider rotations, needs to take the actual distance between p1 and p2 for that
196 return std::abs(p2.x() - p1.x()) * distance;
197}
198
199void View::panScreenSpace(QPoint offset)
200{
201 auto dx = offset.x() * (m_viewport.width() / screenWidth());
202 auto dy = offset.y() * (m_viewport.height() / screenHeight());
203 m_viewport.adjust(dx, dy, dx, dy);
204 constrainViewToScene();
205 updateViewport();
206}
207
209{
210 return m_sceneToScreenTransform;
211}
212
213void View::zoomIn(QPointF screenCenter)
214{
215 setZoomLevel(zoomLevel() + 1, screenCenter);
216}
217
218void View::zoomOut(QPointF screenCenter)
219{
220 setZoomLevel(zoomLevel() - 1, screenCenter);
221}
222
223void View::constrainViewToScene()
224{
225 m_viewport = constrainedViewport(m_viewport);
226}
227
228QRectF View::constrainedViewport(QRectF viewport) const
229{
230 // ensure we don't scale smaller than the bounding box
231 const auto s = std::min(viewport.width() / m_bbox.width(), viewport.height() / m_bbox.height());
232 if (s > 1.0) {
233 viewport.setWidth(viewport.width() / s);
234 viewport.setHeight(viewport.height() / s);
235 }
236
237 // ensure we don't pan outside of the bounding box
238 if (m_bbox.left() < viewport.left() && m_bbox.right() < viewport.right()) {
239 const auto dx = std::min(viewport.left() - m_bbox.left(), viewport.right() - m_bbox.right());
240 viewport.adjust(-dx, 0, -dx, 0);
241 } else if (m_bbox.right() > viewport.right() && m_bbox.left() > viewport.left()) {
242 const auto dx = std::min(m_bbox.right() - viewport.right(), m_bbox.left() - viewport.left());
243 viewport.adjust(dx, 0, dx, 0);
244 }
245
246 if (m_bbox.top() < viewport.top() && m_bbox.bottom() < viewport.bottom()) {
247 const auto dy = std::min(viewport.top() - m_bbox.top(), viewport.bottom() - m_bbox.bottom());
248 viewport.adjust(0, -dy, 0, -dy);
249 } else if (m_bbox.bottom() > viewport.bottom() && m_bbox.top() > viewport.top()) {
250 const auto dy = std::min(m_bbox.bottom() - viewport.bottom(), m_bbox.top() - viewport.top());
251 viewport.adjust(0, dy, 0, dy);
252 }
253
254 return viewport;
255}
256
257double View::mapMetersToScene(double meters) const
258{
259 const auto scale = m_viewport.width() / m_screenWidthInMeters;
260 return meters * scale;
261}
262
263double View::mapMetersToScreen(double meters) const
264{
265 const auto r = meters / m_screenWidthInMeters;
266 return r * m_screenSize.width();
267}
268
269double View::mapScreenToMeters(int pixels) const
270{
271 const auto r = (double)pixels / (double)m_screenSize.width();
272 return r * m_screenWidthInMeters;
273}
274
275double View::panX() const
276{
277 const auto r = (m_viewport.left() - m_bbox.left()) / m_bbox.width();
278 return panWidth() * r;
279}
280
281double View::panY() const
282{
283 const auto r = (m_viewport.top() - m_bbox.top()) / m_bbox.height();
284 return panHeight() * r;
285}
286
287double View::panWidth() const
288{
289 const auto r = m_bbox.width() / m_viewport.width();
290 return screenWidth() * r;
291}
292
293double View::panHeight() const
294{
295 const auto r = m_bbox.height() / m_viewport.height();
296 return screenHeight() * r;
297}
298
299void View::panTopLeft(double x, double y)
300{
301 m_viewport.moveLeft(m_bbox.x() + m_bbox.width() * (x / panWidth()));
302 m_viewport.moveTop(m_bbox.y() + m_bbox.height() * (y / panHeight()));
303 constrainViewToScene();
304 updateViewport();
305}
306
308{
309 return m_deviceTransform;
310}
311
312void View::setDeviceTransform(const QTransform &t)
313{
314 m_deviceTransform = t;
315}
316
318{
319 const auto sceneCenter = mapGeoToScene(OSM::Coordinate(geoCoord.y(), geoCoord.x()));
320 m_viewport.moveCenter(sceneCenter);
321 constrainViewToScene();
322 updateViewport();
323}
324
325void View::updateViewport()
326{
327 // ### this fails for distances above 180° due to OSM::distance wrapping around
328 // doesn't matter for our use-case though, we are looking at much much smaller areas
329 m_screenWidthInMeters = OSM::distance(mapSceneToGeo(QPointF(m_viewport.left(), m_viewport.center().y())), mapSceneToGeo(QPointF(m_viewport.right(), m_viewport.center().y())));
330 if (m_screenWidthInMeters == 0.0) {
331 m_screenWidthInMeters = 1.0; // ensure we don't end up with a division by zero down the line
332 }
333
334 m_sceneToScreenTransform = {};
335 m_sceneToScreenTransform.scale(screenWidth() / (m_viewport.width()), screenHeight() / (m_viewport.height()));
336 m_sceneToScreenTransform.translate(-m_viewport.x(), -m_viewport.y());
337
338 m_screenToSceneTransform = m_sceneToScreenTransform.inverted();
339
340 Q_EMIT transformationChanged();
341}
342
343QDateTime View::beginTime() const
344{
345 return m_beginTime;
346}
347
348void View::setBeginTime(const QDateTime &beginTime)
349{
350 const auto alignedTime = QDateTime(beginTime.date(), {beginTime.time().hour(), beginTime.time().minute()});
351 if (m_beginTime == alignedTime) {
352 return;
353 }
354 m_beginTime = alignedTime;
355 Q_EMIT timeChanged();
356}
357
358QDateTime View::endTime() const
359{
360 return m_endTime;
361}
362
363void View::setEndTime(const QDateTime& endTime)
364{
365 const auto alignedTime = QDateTime(endTime.date(), {endTime.time().hour(), endTime.time().minute()});
366 if (m_endTime == alignedTime) {
367 return;
368 }
369 m_endTime = alignedTime;
370 Q_EMIT timeChanged();
371}
372
374{
375 const auto c = mapSceneToGeo(p);
376 return {c.lonF(), c.latF()};
377}
378
379#include "moc_view.cpp"
Q_INVOKABLE void centerOnGeoCoordinate(QPointF geoCoord)
Center the view on the given geo-coordinate.
Definition view.cpp:317
static QPointF mapGeoToScene(OSM::Coordinate coord)
Map a geographic coordinate to a scene coordinate, ie.
Definition view.cpp:27
Q_INVOKABLE void zoomOut(QPointF screenCenter)
Decrease zoom level by one/scale down by 2x around the screen position center.
Definition view.cpp:218
int level() const
The (floor) level to display.
Definition view.cpp:84
QRectF viewportForZoom(double zoom, QPointF screenCenter) const
Computes the viewport for the given zoom level and screenCenter.
Definition view.cpp:123
QTransform deviceTransform() const
Device tranformation for manual high DPI scaling.
Definition view.cpp:307
Q_INVOKABLE double mapMetersToScreen(double meters) const
Returns how many pixels on screen represent the distance of meters with the current view transformati...
Definition view.cpp:263
static Q_INVOKABLE QPointF mapSceneToGeoPoint(QPointF p)
QML only API due to lack of OSM::Coordinate support there.
Definition view.cpp:373
Q_INVOKABLE void panTopLeft(double x, double y)
Move the viewport to the pan coordinates x and y.
Definition view.cpp:299
double mapMetersToScene(double meters) const
Returns how many units in scene coordinate represent the distance of meters in the current view trans...
Definition view.cpp:257
int screenWidth() const
Screen-space sizes, ie the size of the on-screen area used for displaying.
Definition view.cpp:63
double mapScreenDistanceToSceneDistance(double distance) const
Converts a distance in screen coordinates to a distance in scene coordinates.
Definition view.cpp:191
Q_INVOKABLE double mapScreenToMeters(int pixels) const
Returns how many meters are represented by pixels with the current view transformation.
Definition view.cpp:269
Q_INVOKABLE void zoomIn(QPointF screenCenter)
Increase zoom level by one/scale up by 2x around the screen position center.
Definition view.cpp:213
QPointF mapSceneToScreen(QPointF scenePos) const
Converts a point in scene coordinates to screen coordinates.
Definition view.cpp:175
Q_INVOKABLE void setZoomLevel(double zoom, QPointF screenCenter)
Set the zoom level to zoom, and adjusting it around center position center.
Definition view.cpp:105
QRectF sceneBoundingBox() const
The bounding box of the scene.
Definition view.cpp:142
QTransform sceneToScreenTransform() const
The transformation to apply to scene coordinate to get to the view on screen.
Definition view.cpp:208
Q_INVOKABLE QPointF mapScreenToScene(QPointF screenPos) const
Converts a point in screen coordinates to scene coordinates.
Definition view.cpp:185
static OSM::Coordinate mapSceneToGeo(QPointF p)
Map a scene coordinate to a geographic one, ie.
Definition view.cpp:43
Bounding box, ie.
Definition datatypes.h:95
Coordinate, stored as 1e7 * degree to avoid floating point precision issues, and offset to unsigned v...
Definition datatypes.h:37
OSM-based multi-floor indoor maps for buildings.
KOSM_EXPORT double distance(double lat1, double lon1, double lat2, double lon2)
Distance between two coordinates.
Definition geomath.cpp:16
constexpr double radToDeg(double rad)
Radian to degree conversion.
Definition geomath.h:24
constexpr double degToRad(double deg)
Degree to radian conversion.
Definition geomath.h:19
QDateTime currentDateTime()
QDate date() const const
Q_EMITQ_EMIT
int x() const const
int y() const const
qreal x() const const
qreal y() const const
QPointF bottomLeft() const const
QPointF bottomRight() const const
qreal height() const const
void setHeight(qreal height)
void setWidth(qreal width)
QPointF topLeft() const const
QPointF topRight() const const
qreal width() const const
int height() const const
void setWidth(int width)
int width() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:54:42 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.