1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org>
4 // SPDX-FileCopyrightText: 2008 Jens-Michael Hoffmann <jensmh@gmx.de>
5 // SPDX-FileCopyrightText: 2010-2013 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
6 //
7 
8 
9 #include "ViewportParams.h"
10 
11 #include <QRect>
12 
13 #include <QPainterPath>
14 #include <QRegion>
15 
16 #include "MarbleDebug.h"
17 #include "GeoDataLatLonAltBox.h"
18 #include "SphericalProjection.h"
19 #include "EquirectProjection.h"
20 #include "MercatorProjection.h"
21 #include "GnomonicProjection.h"
22 #include "LambertAzimuthalProjection.h"
23 #include "AzimuthalEquidistantProjection.h"
24 #include "StereographicProjection.h"
25 #include "VerticalPerspectiveProjection.h"
26 
27 
28 namespace Marble
29 {
30 
31 class ViewportParamsPrivate
32 {
33 public:
34     ViewportParamsPrivate( Projection projection,
35                            qreal centerLongitude, qreal centerLatitude,
36                            int radius,
37                            const QSize &size );
38 
39     static const AbstractProjection *abstractProjection( Projection projection );
40 
41     // These two go together.  m_currentProjection points to one of
42     // the static Projection classes at the bottom.
43     Projection           m_projection;
44     const AbstractProjection *m_currentProjection;
45 
46     // Parameters that determine the painting
47     qreal                m_centerLongitude;
48     qreal                m_centerLatitude;
49     qreal                m_heading;
50     Quaternion           m_planetAxis;   // Position, coded in a quaternion
51     matrix               m_planetAxisMatrix;
52     int                  m_radius;       // Zoom level (pixels / globe radius)
53     qreal                m_angularResolution;
54 
55     QSize                m_size;         // width, height
56 
57 
58     bool                 m_dirtyBox;
59     GeoDataLatLonAltBox  m_viewLatLonAltBox;
60 
61     static const SphericalProjection  s_sphericalProjection;
62     static const EquirectProjection   s_equirectProjection;
63     static const MercatorProjection   s_mercatorProjection;
64     static const GnomonicProjection   s_gnomonicProjection;
65     static const StereographicProjection   s_stereographicProjection;
66     static const LambertAzimuthalProjection   s_lambertAzimuthalProjection;
67     static const AzimuthalEquidistantProjection   s_azimuthalEquidistantProjection;
68     static const VerticalPerspectiveProjection   s_verticalPerspectiveProjection;
69 
70     GeoDataCoordinates   m_focusPoint;
71 };
72 
73 const SphericalProjection  ViewportParamsPrivate::s_sphericalProjection;
74 const EquirectProjection   ViewportParamsPrivate::s_equirectProjection;
75 const MercatorProjection   ViewportParamsPrivate::s_mercatorProjection;
76 const GnomonicProjection   ViewportParamsPrivate::s_gnomonicProjection;
77 const StereographicProjection   ViewportParamsPrivate::s_stereographicProjection;
78 const LambertAzimuthalProjection   ViewportParamsPrivate::s_lambertAzimuthalProjection;
79 const AzimuthalEquidistantProjection   ViewportParamsPrivate::s_azimuthalEquidistantProjection;
80 const VerticalPerspectiveProjection   ViewportParamsPrivate::s_verticalPerspectiveProjection;
81 
ViewportParamsPrivate(Projection projection,qreal centerLongitude,qreal centerLatitude,int radius,const QSize & size)82 ViewportParamsPrivate::ViewportParamsPrivate( Projection projection,
83                                               qreal centerLongitude, qreal centerLatitude,
84                                               int radius,
85                                               const QSize &size )
86     : m_projection( projection ),
87       m_currentProjection( abstractProjection( projection ) ),
88       m_centerLongitude( centerLongitude ),
89       m_centerLatitude( centerLatitude ),
90       m_heading( 0 ),
91       m_planetAxis(),
92       m_planetAxisMatrix(),
93       m_radius( radius ),
94       m_angularResolution(4.0 / abs(m_radius)),
95       m_size( size ),
96       m_dirtyBox( true ),
97       m_viewLatLonAltBox()
98 {
99 }
100 
abstractProjection(Projection projection)101 const AbstractProjection *ViewportParamsPrivate::abstractProjection(Projection projection)
102 {
103     switch ( projection ) {
104     case Spherical:
105         return &s_sphericalProjection;
106     case Equirectangular:
107         return &s_equirectProjection;
108     case Mercator:
109         return &s_mercatorProjection;
110     case Gnomonic:
111         return &s_gnomonicProjection;
112     case Stereographic:
113         return &s_stereographicProjection;
114     case LambertAzimuthal:
115         return &s_lambertAzimuthalProjection;
116     case AzimuthalEquidistant:
117         return &s_azimuthalEquidistantProjection;
118     case VerticalPerspective:
119         return &s_verticalPerspectiveProjection;
120     }
121 
122     return nullptr;
123 }
124 
125 
ViewportParams()126 ViewportParams::ViewportParams()
127     : d( new ViewportParamsPrivate( Spherical, 0, 0, 2000, QSize( 100, 100 ) ) )
128 {
129     centerOn( d->m_centerLongitude, d->m_centerLatitude );
130 }
131 
ViewportParams(Projection projection,qreal centerLongitude,qreal centerLatitude,int radius,const QSize & size)132 ViewportParams::ViewportParams( Projection projection,
133                                 qreal centerLongitude, qreal centerLatitude,
134                                 int radius,
135                                 const QSize &size )
136     : d( new ViewportParamsPrivate( projection, centerLongitude, centerLatitude, radius, size ) )
137 {
138     centerOn( d->m_centerLongitude, d->m_centerLatitude );
139 }
140 
~ViewportParams()141 ViewportParams::~ViewportParams()
142 {
143     delete d;
144 }
145 
146 
147 // ================================================================
148 //                    Getters and setters
149 
150 
projection() const151 Projection ViewportParams::projection() const
152 {
153     return d->m_projection;
154 }
155 
currentProjection() const156 const AbstractProjection *ViewportParams::currentProjection() const
157 {
158     return d->m_currentProjection;
159 }
160 
setProjection(Projection newProjection)161 void ViewportParams::setProjection(Projection newProjection)
162 {
163     d->m_projection = newProjection;
164     d->m_currentProjection = ViewportParamsPrivate::abstractProjection( newProjection );
165 
166     // We now need to reset the planetAxis to make sure
167     // that it's a valid axis orientation!
168     // So this line is important (although it might look odd) ! :
169     centerOn( d->m_centerLongitude, d->m_centerLatitude );
170 }
171 
polarity() const172 int ViewportParams::polarity() const
173 {
174     // For mercator this just gives the extreme latitudes
175     // instead of the actual poles but it works fine as well:
176     GeoDataCoordinates northPole( 0.0, +currentProjection()->maxLat() );
177     GeoDataCoordinates southPole( 0.0, -currentProjection()->maxLat() );
178 
179     bool globeHidesN, globeHidesS;
180     qreal x;
181     qreal yN, yS;
182 
183     currentProjection()->screenCoordinates( northPole, this,
184                                           x, yN, globeHidesN );
185     currentProjection()->screenCoordinates( southPole, this,
186                                           x, yS, globeHidesS );
187 
188     int polarity = 0;
189 
190     // case of the flat map:
191     if ( !globeHidesN && !globeHidesS ) {
192         if ( yN < yS ) {
193             polarity = +1;
194         }
195         if ( yS < yN ) {
196             polarity = -1;
197         }
198     }
199     else {
200         if ( !globeHidesN && yN < height() / 2 ) {
201             polarity = +1;
202         }
203         if ( !globeHidesN && yN > height() / 2 ) {
204             polarity = -1;
205         }
206         if ( !globeHidesS && yS > height() / 2 ) {
207             polarity = +1;
208         }
209         if ( !globeHidesS && yS < height() / 2 ) {
210             polarity = -1;
211         }
212     }
213 
214     return polarity;
215 }
216 
radius() const217 int ViewportParams::radius() const
218 {
219     return d->m_radius;
220 }
221 
setRadius(int newRadius)222 void ViewportParams::setRadius(int newRadius)
223 {
224     if ( newRadius > 0 ) {
225         d->m_dirtyBox = true;
226 
227         d->m_radius = newRadius;
228         d->m_angularResolution = 4.0 / d->m_radius;
229     }
230 }
231 
centerOn(qreal lon,qreal lat)232 void ViewportParams::centerOn( qreal lon, qreal lat )
233 {
234     if ( !d->m_currentProjection->traversablePoles() ) {
235         if ( lat > d->m_currentProjection->maxLat() )
236             lat = d->m_currentProjection->maxLat();
237 
238         if ( lat < d->m_currentProjection->minLat() )
239             lat = d->m_currentProjection->minLat();
240     } else {
241         while ( lat > M_PI )
242             lat -= 2 * M_PI;
243         while ( lat < -M_PI )
244             lat += 2 * M_PI;
245     }
246 
247     while ( lon > M_PI )
248         lon -= 2 * M_PI;
249     while ( lon < -M_PI )
250         lon += 2 * M_PI;
251 
252     d->m_centerLongitude = lon;
253     d->m_centerLatitude = lat;
254 
255     const Quaternion roll = Quaternion::fromEuler( 0, 0, d->m_heading );
256     const Quaternion quat = Quaternion::fromEuler( -lat, lon, 0.0 );
257 
258     d->m_planetAxis = quat * roll;
259     d->m_planetAxis.normalize();
260 
261     d->m_dirtyBox = true;
262     d->m_planetAxis.inverse().toMatrix( d->m_planetAxisMatrix );
263     d->m_planetAxis.normalize();
264 }
265 
setHeading(qreal heading)266 void ViewportParams::setHeading( qreal heading )
267 {
268     d->m_heading = heading;
269 
270     const Quaternion roll = Quaternion::fromEuler( 0, 0, heading );
271 
272     const qreal centerLat = centerLatitude();
273     const qreal centerLon = centerLongitude();
274 
275     const Quaternion quat = Quaternion::fromEuler( -centerLat, centerLon, 0 );
276 
277     d->m_planetAxis = quat * roll;
278     d->m_planetAxis.normalize();
279 
280     d->m_dirtyBox = true;
281     d->m_planetAxis.inverse().toMatrix( d->m_planetAxisMatrix );
282     d->m_planetAxis.normalize();
283 }
284 
heading() const285 qreal ViewportParams::heading() const
286 {
287     return d->m_heading;
288 }
289 
planetAxis() const290 Quaternion ViewportParams::planetAxis() const
291 {
292     return d->m_planetAxis;
293 }
294 
planetAxisMatrix() const295 const matrix &ViewportParams::planetAxisMatrix() const
296 {
297     return d->m_planetAxisMatrix;
298 }
299 
width() const300 int ViewportParams::width()  const
301 {
302     return d->m_size.width();
303 }
304 
height() const305 int ViewportParams::height() const
306 {
307     return d->m_size.height();
308 }
309 
size() const310 QSize ViewportParams::size() const
311 {
312     return d->m_size;
313 }
314 
315 
setWidth(int newWidth)316 void ViewportParams::setWidth(int newWidth)
317 {
318     setSize( QSize( newWidth, height() ) );
319 }
320 
setHeight(int newHeight)321 void ViewportParams::setHeight(int newHeight)
322 {
323     setSize( QSize( width(), newHeight ) );
324 }
325 
setSize(const QSize & newSize)326 void ViewportParams::setSize(const QSize& newSize)
327 {
328     if ( newSize == d->m_size )
329         return;
330 
331     d->m_dirtyBox = true;
332 
333     d->m_size = newSize;
334 }
335 
336 // ================================================================
337 //                        Other functions
338 
centerLongitude() const339 qreal ViewportParams::centerLongitude() const
340 {
341     return d->m_centerLongitude;
342 }
343 
centerLatitude() const344 qreal ViewportParams::centerLatitude() const
345 {
346     return d->m_centerLatitude;
347 }
348 
viewLatLonAltBox() const349 const GeoDataLatLonAltBox& ViewportParams::viewLatLonAltBox() const
350 {
351     if (d->m_dirtyBox) {
352         d->m_viewLatLonAltBox = d->m_currentProjection->latLonAltBox( QRect( QPoint( 0, 0 ),
353                         d->m_size ),
354                         this );
355         d->m_dirtyBox = false;
356     }
357 
358     return d->m_viewLatLonAltBox;
359 }
360 
latLonAltBox(const QRect & screenRect) const361 GeoDataLatLonAltBox ViewportParams::latLonAltBox( const QRect &screenRect ) const
362 {
363     return d->m_currentProjection->latLonAltBox( screenRect, this );
364 }
365 
angularResolution() const366 qreal ViewportParams::angularResolution() const
367 {
368     // We essentially divide the diameter by 180 deg and
369     // take half of the result as a guess for the angle per pixel resolution.
370     // d->m_angularResolution = 0.25 * M_PI / fabs( (qreal)(d->m_radius);
371     return d->m_angularResolution;
372 }
373 
resolves(const GeoDataLatLonBox & latLonBox,qreal pixel) const374 bool ViewportParams::resolves ( const GeoDataLatLonBox &latLonBox, qreal pixel ) const
375 {
376     return latLonBox.width() + latLonBox.height() > pixel * d->m_angularResolution;
377 }
378 
379 
resolves(const GeoDataLatLonAltBox & latLonAltBox,qreal pixel,qreal altitude) const380 bool ViewportParams::resolves ( const GeoDataLatLonAltBox &latLonAltBox, qreal pixel, qreal altitude ) const
381 {
382     return    latLonAltBox.width() + latLonAltBox.height() > pixel * d->m_angularResolution
383            || latLonAltBox.maxAltitude() - latLonAltBox.minAltitude() > altitude;
384 }
385 
resolves(const GeoDataCoordinates & coord1,const GeoDataCoordinates & coord2) const386 bool ViewportParams::resolves ( const GeoDataCoordinates &coord1,
387                                 const GeoDataCoordinates &coord2 ) const
388 {
389     qreal lon1, lat1;
390     coord1.geoCoordinates( lon1, lat1 );
391 
392     qreal lon2, lat2;
393     coord2.geoCoordinates( lon2, lat2 );
394 
395     // We take the manhattan length as an approximation for the distance
396     return ( fabs( lon2 - lon1 ) + fabs( lat2 - lat1 ) > d->m_angularResolution );
397 }
398 
399 
screenCoordinates(const qreal lon,const qreal lat,qreal & x,qreal & y) const400 bool ViewportParams::screenCoordinates( const qreal lon, const qreal lat,
401                         qreal &x, qreal &y ) const
402 {
403     return d->m_currentProjection->screenCoordinates( lon, lat, this, x, y );
404 }
405 
screenCoordinates(const GeoDataCoordinates & geopoint,qreal & x,qreal & y,bool & globeHidesPoint) const406 bool ViewportParams::screenCoordinates( const GeoDataCoordinates &geopoint,
407                         qreal &x, qreal &y,
408                         bool &globeHidesPoint ) const
409 {
410     return d->m_currentProjection->screenCoordinates( geopoint, this, x, y, globeHidesPoint );
411 }
412 
screenCoordinates(const GeoDataCoordinates & geopoint,qreal & x,qreal & y) const413 bool ViewportParams::screenCoordinates( const GeoDataCoordinates &geopoint,
414                         qreal &x, qreal &y ) const
415 {
416     return d->m_currentProjection->screenCoordinates( geopoint, this, x, y );
417 }
418 
screenCoordinates(const GeoDataCoordinates & coordinates,qreal * x,qreal & y,int & pointRepeatNum,const QSizeF & size,bool & globeHidesPoint) const419 bool ViewportParams::screenCoordinates( const GeoDataCoordinates &coordinates,
420                         qreal *x, qreal &y, int &pointRepeatNum,
421                         const QSizeF& size,
422                         bool &globeHidesPoint ) const
423 {
424     return d->m_currentProjection->screenCoordinates( coordinates, this, x, y, pointRepeatNum, size, globeHidesPoint );
425 }
426 
427 
screenCoordinates(const GeoDataLineString & lineString,QVector<QPolygonF * > & polygons) const428 bool ViewportParams::screenCoordinates( const GeoDataLineString &lineString,
429                         QVector<QPolygonF*> &polygons ) const
430 {
431     return d->m_currentProjection->screenCoordinates( lineString, this, polygons );
432 }
433 
geoCoordinates(const int x,const int y,qreal & lon,qreal & lat,GeoDataCoordinates::Unit unit) const434 bool ViewportParams::geoCoordinates( const int x, const int y,
435                      qreal &lon, qreal &lat,
436                      GeoDataCoordinates::Unit unit ) const
437 {
438     return d->m_currentProjection->geoCoordinates( x, y, this, lon, lat, unit );
439 }
440 
mapCoversViewport() const441 bool  ViewportParams::mapCoversViewport() const
442 {
443     return d->m_currentProjection->mapCoversViewport( this );
444 }
445 
mapShape() const446 QPainterPath ViewportParams::mapShape() const
447 {
448     return d->m_currentProjection->mapShape( this );
449 }
450 
mapRegion() const451 QRegion ViewportParams::mapRegion() const
452 {
453     return d->m_currentProjection->mapRegion( this );
454 }
455 
focusPoint() const456 GeoDataCoordinates ViewportParams::focusPoint() const
457 {
458     if (d->m_focusPoint.isValid()) {
459         return d->m_focusPoint;
460     }
461     else {
462        const qreal lon = d->m_centerLongitude;
463        const qreal lat = d->m_centerLatitude;
464 
465        return GeoDataCoordinates(lon, lat, 0.0, GeoDataCoordinates::Radian);
466     }
467 
468 }
469 
setFocusPoint(const GeoDataCoordinates & focusPoint)470 void ViewportParams::setFocusPoint(const GeoDataCoordinates &focusPoint)
471 {
472     d->m_focusPoint = focusPoint;
473 }
474 
resetFocusPoint()475 void ViewportParams::resetFocusPoint()
476 {
477     d->m_focusPoint = GeoDataCoordinates();
478 }
479 
480 }
481