KOSMIndoorMap

view.cpp
1 /*
2  SPDX-FileCopyrightText: 2020 Volker Krause <[email protected]>
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 
13 using namespace KOSMIndoorMap;
14 
15 static constexpr const double SceneWorldSize = 256.0; // size of the scene when containing the full world
16 static constexpr const double LatitudeLimit = 85.05112879806592; // invtan(sinh(pi)) + radToDeg
17 static constexpr const auto MaxZoomFactor = 21; // 2^MaxZoomFactor subdivisions of the scene space
18 
19 View::View(QObject *parent)
20  : QObject(parent)
21 {
22  setBeginTime(QDateTime::currentDateTime());
23 }
24 
25 View::~View() = default;
26 
27 QPointF View::mapGeoToScene(OSM::Coordinate coord) const
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 
36 QRectF View::mapGeoToScene(OSM::BoundingBox box) const
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 
43 OSM::Coordinate View::mapSceneToGeo(QPointF p) const
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 
51 OSM::BoundingBox View::mapSceneToGeo(const QRectF &box) const
52 {
53  const auto c1 = mapSceneToGeo(box.bottomLeft());
54  const auto c2 = mapSceneToGeo(box.topRight());
55  return OSM::BoundingBox(c1, c2);
56 }
57 
58 int View::screenHeight() const
59 {
60  return m_screenSize.height();
61 }
62 
63 int View::screenWidth() const
64 {
65  return m_screenSize.width();
66 }
67 
68 void 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  Q_EMIT transformationChanged();
82 }
83 
84 int View::level() const
85 {
86  return m_level;
87 }
88 
89 void View::setLevel(int level)
90 {
91  if (m_level == level) {
92  return;
93  }
94 
95  m_level = level;
96  Q_EMIT floorLevelChanged();
97 }
98 
99 double View::zoomLevel() const
100 {
101  const auto dx = m_viewport.width() / (screenWidth() / SceneWorldSize) / 360.0;
102  return - std::log2(dx);
103 }
104 
105 void View::setZoomLevel(double zoom, QPointF screenCenter)
106 {
107  auto z = std::pow(2.0, - std::min(zoom, (double)MaxZoomFactor));
108  const auto dx = ((screenWidth() / SceneWorldSize) * 360.0 * z) - m_viewport.width();
109  const auto dy = ((screenHeight() / SceneWorldSize) * 360.0 * z) - m_viewport.height();
110 
111  const auto centerScene = mapScreenToScene(screenCenter);
112  if (!m_viewport.contains(centerScene)) {
113  return; // invalid input
114  }
115 
116  const auto xr = (centerScene.x() - m_viewport.x()) / m_viewport.width();
117  const auto yr = (centerScene.y() - m_viewport.y()) / m_viewport.height();
118 
119  m_viewport.adjust(-xr * dx, -yr * dy, (1-xr) * dx, (1-yr) * dy);
120  constrainViewToScene();
121  Q_EMIT transformationChanged();
122 }
123 
124 QRectF View::viewport() const
125 {
126  return m_viewport;
127 }
128 
129 void View::setViewport(const QRectF &viewport)
130 {
131  m_viewport = viewport;
132  constrainViewToScene();
133 }
134 
135 QRectF View::sceneBoundingBox() const
136 {
137  return m_bbox;
138 }
139 
140 void View::setSceneBoundingBox(OSM::BoundingBox bbox)
141 {
142  setSceneBoundingBox(mapGeoToScene(bbox));
143 }
144 
145 void View::setSceneBoundingBox(const QRectF &bbox)
146 {
147  if (m_bbox == bbox) {
148  return;
149  }
150  m_bbox = bbox;
151 
152  // scale to fit horizontally
153  m_viewport = bbox;
154  const auto screenAspectRatio = (double)screenWidth() / (double)screenHeight();
155  m_viewport.setHeight(m_viewport.width() / screenAspectRatio);
156 
157  // if necessary, scale to fit vertically
158  if (m_viewport.height() > m_bbox.height()) {
159  const auto dy = (double)m_bbox.height() / (double)m_viewport.height();
160  m_viewport.setHeight(m_viewport.height() * dy);
161  m_viewport.setWidth(m_viewport.width() * dy);
162  }
163 
164  Q_EMIT transformationChanged();
165 }
166 
167 
168 QPointF View::mapSceneToScreen(QPointF scenePos) const
169 {
170  return sceneToScreenTransform().map(scenePos);
171 }
172 
173 QRectF View::mapSceneToScreen(const QRectF &sceneRect) const
174 {
175  return QRectF(mapSceneToScreen(sceneRect.topLeft()), mapSceneToScreen(sceneRect.bottomRight()));
176 }
177 
178 QPointF View::mapScreenToScene(QPointF screenPos) const
179 {
180  // TODO this can be implemented more efficiently
181  return sceneToScreenTransform().inverted().map(screenPos);
182 }
183 
184 double View::mapScreenDistanceToSceneDistance(double distance) const
185 {
186  const auto p1 = mapScreenToScene(m_viewport.center());
187  const auto p2 = mapScreenToScene(m_viewport.center() + QPointF(1.0, 0));
188  // ### does not consider rotations, needs to take the actual distance between p1 and p2 for that
189  return std::abs(p2.x() - p1.x()) * distance;
190 }
191 
192 void View::panScreenSpace(QPoint offset)
193 {
194  auto dx = offset.x() * (m_viewport.width() / screenWidth());
195  auto dy = offset.y() * (m_viewport.height() / screenHeight());
196  m_viewport.adjust(dx, dy, dx, dy);
197  constrainViewToScene();
198 }
199 
200 QTransform View::sceneToScreenTransform() const
201 {
202  QTransform t;
203  t.scale(screenWidth() / (m_viewport.width()), screenHeight() / (m_viewport.height()));
204  t.translate(-m_viewport.x(), -m_viewport.y());
205  return t;
206 }
207 
208 void View::zoomIn(QPointF screenCenter)
209 {
210  setZoomLevel(zoomLevel() + 1, screenCenter);
211 }
212 
213 void View::zoomOut(QPointF screenCenter)
214 {
215  setZoomLevel(zoomLevel() - 1, screenCenter);
216 }
217 
218 void View::constrainViewToScene()
219 {
220  // ensure we don't scale smaller than the bounding box
221  const auto s = std::min(m_viewport.width() / m_bbox.width(), m_viewport.height() / m_bbox.height());
222  if (s > 1.0) {
223  m_viewport.setWidth(m_viewport.width() / s);
224  m_viewport.setHeight(m_viewport.height() / s);
225  }
226 
227  // ensure we don't pan outside of the bounding box
228  if (m_bbox.left() < m_viewport.left() && m_bbox.right() < m_viewport.right()) {
229  const auto dx = std::min(m_viewport.left() - m_bbox.left(), m_viewport.right() - m_bbox.right());
230  m_viewport.adjust(-dx, 0, -dx, 0);
231  } else if (m_bbox.right() > m_viewport.right() && m_bbox.left() > m_viewport.left()) {
232  const auto dx = std::min(m_bbox.right() - m_viewport.right(), m_bbox.left() - m_viewport.left());
233  m_viewport.adjust(dx, 0, dx, 0);
234  }
235 
236  if (m_bbox.top() < m_viewport.top() && m_bbox.bottom() < m_viewport.bottom()) {
237  const auto dy = std::min(m_viewport.top() - m_bbox.top(), m_viewport.bottom() - m_bbox.bottom());
238  m_viewport.adjust(0, -dy, 0, -dy);
239  } else if (m_bbox.bottom() > m_viewport.bottom() && m_bbox.top() > m_viewport.top()) {
240  const auto dy = std::min(m_bbox.bottom() - m_viewport.bottom(), m_bbox.top() - m_viewport.top());
241  m_viewport.adjust(0, dy, 0, dy);
242  }
243 }
244 
245 double View::mapMetersToScene(double meters) const
246 {
247  // ### this fails for distances above 180° due to OSM::distance wrapping around
248  // doesn't matter for our use-case though, we are looking at much much smaller areas
249  const auto d = OSM::distance(mapSceneToGeo(QPointF(m_viewport.left(), m_viewport.center().y())), mapSceneToGeo(QPointF(m_viewport.right(), m_viewport.center().y())));
250  const auto scale = m_viewport.width() / d;
251  return meters * scale;
252 }
253 
254 double View::mapMetersToScreen(double meters) const
255 {
256  const auto d = OSM::distance(mapSceneToGeo(QPointF(m_viewport.left(), m_viewport.center().y())), mapSceneToGeo(QPointF(m_viewport.right(), m_viewport.center().y())));
257  const auto r = meters / d;
258  return r * m_screenSize.width();
259 }
260 
261 double View::mapScreenToMeters(int pixels) const
262 {
263  const auto d = OSM::distance(mapSceneToGeo(QPointF(m_viewport.left(), m_viewport.center().y())), mapSceneToGeo(QPointF(m_viewport.right(), m_viewport.center().y())));
264  const auto r = (double)pixels / (double)m_screenSize.width();
265  return d * r;
266 }
267 
268 double View::panX() const
269 {
270  const auto r = (m_viewport.left() - m_bbox.left()) / m_bbox.width();
271  return panWidth() * r;
272 }
273 
274 double View::panY() const
275 {
276  const auto r = (m_viewport.top() - m_bbox.top()) / m_bbox.height();
277  return panHeight() * r;
278 }
279 
280 double View::panWidth() const
281 {
282  const auto r = m_bbox.width() / m_viewport.width();
283  return screenWidth() * r;
284 }
285 
286 double View::panHeight() const
287 {
288  const auto r = m_bbox.height() / m_viewport.height();
289  return screenHeight() * r;
290 }
291 
292 void View::panTopLeft(double x, double y)
293 {
294  m_viewport.moveLeft(m_bbox.x() + m_bbox.width() * (x / panWidth()));
295  m_viewport.moveTop(m_bbox.y() + m_bbox.height() * (y / panHeight()));
296  constrainViewToScene();
297 }
298 
299 QTransform View::deviceTransform() const
300 {
301  return m_deviceTransform;
302 }
303 
304 void View::setDeviceTransform(const QTransform &t)
305 {
306  m_deviceTransform = t;
307 }
308 
309 void View::centerOnGeoCoordinate(QPointF geoCoord)
310 {
311  const auto sceneCenter = mapGeoToScene(OSM::Coordinate(geoCoord.y(), geoCoord.x()));
312  m_viewport.moveCenter(sceneCenter);
313  constrainViewToScene();
314  Q_EMIT transformationChanged();
315 }
316 
317 QDateTime View::beginTime() const
318 {
319  return m_beginTime;
320 }
321 
322 void View::setBeginTime(const QDateTime &beginTime)
323 {
324  const auto alignedTime = QDateTime(beginTime.date(), {beginTime.time().hour(), beginTime.time().minute()});
325  if (m_beginTime == alignedTime) {
326  return;
327  }
328  m_beginTime = alignedTime;
329  Q_EMIT timeChanged();
330 }
331 
332 QDateTime View::endTime() const
333 {
334  return m_endTime;
335 }
336 
337 void View::setEndTime(const QDateTime& endTime)
338 {
339  const auto alignedTime = QDateTime(endTime.date(), {endTime.time().hour(), endTime.time().minute()});
340  if (m_endTime == alignedTime) {
341  return;
342  }
343  m_endTime = alignedTime;
344  Q_EMIT timeChanged();
345 }
OSM-based multi-floor indoor maps for buildings.
int minute() const const
int width() const const
QTime time() const const
Coordinate, stored as 1e7 * degree to avoid floating point precision issues, and offset to unsigned v...
Definition: datatypes.h:37
int x() const const
int y() const const
void setHeight(qreal height)
QTransform & translate(qreal dx, qreal dy)
qreal x() const const
qreal y() const const
QTransform & scale(qreal sx, qreal sy)
void setWidth(int width)
QPointF topLeft() const const
constexpr double degToRad(double deg)
Degree to radian conversion.
Definition: geomath.h:19
QPointF topRight() const const
int hour() const const
QDateTime currentDateTime()
QDate date() const const
QPointF bottomLeft() const const
int height() const const
constexpr double radToDeg(double rad)
Radian to degree conversion.
Definition: geomath.h:24
QPointF bottomRight() const const
KOSM_EXPORT double distance(double lat1, double lon1, double lat2, double lon2)
Distance between two coordinates.
Definition: geomath.cpp:17
Bounding box, ie.
Definition: datatypes.h:95
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Oct 25 2021 23:04:00 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.