1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org>
4 // SPDX-FileCopyrightText: 2007-2012 Torsten Rahn <rahn@kde.org>
5 // SPDX-FileCopyrightText: 2012 Cezar Mocan <mocancezar@gmail.com>
6 //
7 
8 // Local
9 #include "AbstractProjection.h"
10 
11 #include "AbstractProjection_p.h"
12 
13 #include "MarbleDebug.h"
14 #include <QRegion>
15 #include <QPainterPath>
16 
17 // Marble
18 #include "GeoDataLineString.h"
19 #include "GeoDataLinearRing.h"
20 #include "GeoDataLatLonAltBox.h"
21 #include "ViewportParams.h"
22 
23 using namespace Marble;
24 
AbstractProjection()25 AbstractProjection::AbstractProjection()
26     : d_ptr( new AbstractProjectionPrivate( this ) )
27 {
28 }
29 
AbstractProjection(AbstractProjectionPrivate * dd)30 AbstractProjection::AbstractProjection( AbstractProjectionPrivate* dd )
31     : d_ptr( dd )
32 {
33 }
34 
~AbstractProjection()35 AbstractProjection::~AbstractProjection()
36 {
37 }
38 
AbstractProjectionPrivate(AbstractProjection * parent)39 AbstractProjectionPrivate::AbstractProjectionPrivate( AbstractProjection * parent )
40     : m_maxLat(0),
41       m_minLat(0),
42       m_previousResolution(-1),
43       m_level(-1),
44       q_ptr( parent)
45 {
46 }
47 
levelForResolution(qreal resolution) const48 int AbstractProjectionPrivate::levelForResolution(qreal resolution) const {
49     if (m_previousResolution == resolution) return m_level;
50 
51     m_previousResolution = resolution;
52 
53     if (resolution < 0.0000005) m_level = 17;
54     else if (resolution < 0.0000010) m_level = 16;
55     else if (resolution < 0.0000020) m_level = 15;
56     else if (resolution < 0.0000040) m_level = 14;
57     else if (resolution < 0.0000080) m_level = 13;
58     else if (resolution < 0.0000160) m_level = 12;
59     else if (resolution < 0.0000320) m_level = 11;
60     else if (resolution < 0.0000640) m_level = 10;
61     else if (resolution < 0.0001280) m_level = 9;
62     else if (resolution < 0.0002560) m_level = 8;
63     else if (resolution < 0.0005120) m_level = 7;
64     else if (resolution < 0.0010240) m_level = 6;
65     else if (resolution < 0.0020480) m_level = 5;
66     else if (resolution < 0.0040960) m_level = 4;
67     else if (resolution < 0.0081920) m_level = 3;
68     else if (resolution < 0.0163840) m_level = 2;
69     else m_level =  1;
70 
71     return m_level;
72 }
73 
maxValidLat() const74 qreal AbstractProjection::maxValidLat() const
75 {
76     return +90.0 * DEG2RAD;
77 }
78 
maxLat() const79 qreal AbstractProjection::maxLat() const
80 {
81     Q_D(const AbstractProjection );
82     return d->m_maxLat;
83 }
84 
setMaxLat(qreal maxLat)85 void AbstractProjection::setMaxLat( qreal maxLat )
86 {
87     if ( maxLat < maxValidLat() ) {
88         mDebug() << Q_FUNC_INFO << "Trying to set maxLat to a value that is out of the valid range.";
89         return;
90     }
91 
92     Q_D( AbstractProjection );
93     d->m_maxLat = maxLat;
94 }
95 
minValidLat() const96 qreal AbstractProjection::minValidLat() const
97 {
98     return -90.0 * DEG2RAD;
99 }
100 
minLat() const101 qreal AbstractProjection::minLat() const
102 {
103     Q_D( const AbstractProjection );
104     return d->m_minLat;
105 }
106 
setMinLat(qreal minLat)107 void AbstractProjection::setMinLat( qreal minLat )
108 {
109     if ( minLat < minValidLat() ) {
110         mDebug() << Q_FUNC_INFO << "Trying to set minLat to a value that is out of the valid range.";
111         return;
112     }
113 
114     Q_D( AbstractProjection );
115     d->m_minLat = minLat;
116 }
117 
repeatableX() const118 bool AbstractProjection::repeatableX() const
119 {
120     return false;
121 }
122 
traversablePoles() const123 bool AbstractProjection::traversablePoles() const
124 {
125     return false;
126 }
127 
traversableDateLine() const128 bool AbstractProjection::traversableDateLine() const
129 {
130     return false;
131 }
132 
preservationType() const133 AbstractProjection::PreservationType AbstractProjection::preservationType() const
134 {
135     return NoPreservation;
136 }
137 
isOrientedNormal() const138 bool AbstractProjection::isOrientedNormal() const
139 {
140     return true;
141 }
142 
isClippedToSphere() const143 bool AbstractProjection::isClippedToSphere() const
144 {
145     return false;
146 }
147 
clippingRadius() const148 qreal AbstractProjection::clippingRadius() const
149 {
150     return 0;
151 }
152 
153 
screenCoordinates(const qreal lon,const qreal lat,const ViewportParams * viewport,qreal & x,qreal & y) const154 bool AbstractProjection::screenCoordinates( const qreal lon, const qreal lat,
155                                             const ViewportParams *viewport,
156                                             qreal &x, qreal &y ) const
157 {
158     bool globeHidesPoint;
159     GeoDataCoordinates geopoint(lon, lat);
160     return screenCoordinates( geopoint, viewport, x, y, globeHidesPoint );
161 }
162 
screenCoordinates(const GeoDataCoordinates & geopoint,const ViewportParams * viewport,qreal & x,qreal & y) const163 bool AbstractProjection::screenCoordinates( const GeoDataCoordinates &geopoint,
164                                             const ViewportParams *viewport,
165                                             qreal &x, qreal &y ) const
166 {
167     bool globeHidesPoint;
168 
169     return screenCoordinates( geopoint, viewport, x, y, globeHidesPoint );
170 }
171 
latLonAltBox(const QRect & screenRect,const ViewportParams * viewport) const172 GeoDataLatLonAltBox AbstractProjection::latLonAltBox( const QRect& screenRect,
173                                                       const ViewportParams *viewport ) const
174 {
175     // For the case where the whole viewport gets covered there is a
176     // pretty dirty and generic detection algorithm:
177 
178     // Move along the screenborder and save the highest and lowest lon-lat values.
179     QRect projectedRect = mapRegion( viewport ).boundingRect();
180     QRect mapRect = screenRect.intersected( projectedRect );
181 
182     GeoDataLineString boundingLineString;
183 
184     qreal lon, lat;
185 
186     for ( int x = mapRect.left(); x < mapRect.right(); x += latLonAltBoxSamplingRate ) {
187         if ( geoCoordinates( x, mapRect.bottom(), viewport, lon, lat,
188                              GeoDataCoordinates::Radian ) ) {
189             boundingLineString << GeoDataCoordinates( lon, lat );
190         }
191 
192         if ( geoCoordinates( x, mapRect.top(),
193                              viewport, lon, lat, GeoDataCoordinates::Radian ) ) {
194             boundingLineString << GeoDataCoordinates( lon, lat );
195         }
196     }
197 
198     if ( geoCoordinates( mapRect.right(), mapRect.top(), viewport, lon, lat,
199                          GeoDataCoordinates::Radian ) ) {
200         boundingLineString << GeoDataCoordinates( lon, lat );
201     }
202 
203     if ( geoCoordinates( mapRect.right(), mapRect.bottom(),
204                          viewport, lon, lat, GeoDataCoordinates::Radian ) ) {
205         boundingLineString << GeoDataCoordinates( lon, lat );
206     }
207 
208     for ( int y = mapRect.bottom(); y < mapRect.top(); y += latLonAltBoxSamplingRate ) {
209         if ( geoCoordinates( mapRect.left(), y, viewport, lon, lat,
210                              GeoDataCoordinates::Radian ) ) {
211             boundingLineString << GeoDataCoordinates( lon, lat );
212         }
213 
214         if ( geoCoordinates( mapRect.right(), y,
215                              viewport, lon, lat, GeoDataCoordinates::Radian ) ) {
216             boundingLineString << GeoDataCoordinates( lon, lat );
217         }
218     }
219 
220     GeoDataLatLonAltBox latLonAltBox = boundingLineString.latLonAltBox();
221 
222     // Now we need to check whether maxLat (e.g. the north pole) gets displayed
223     // inside the viewport.
224 
225     // We need a point on the screen at maxLat that definitely gets displayed:
226 
227     // FIXME: Some of the following code can be safely removed as soon as we properly handle
228     //        GeoDataLinearRing::latLonAltBox().
229     qreal averageLongitude = ( latLonAltBox.west() + latLonAltBox.east() ) / 2.0;
230 
231     GeoDataCoordinates maxLatPoint( averageLongitude, maxLat(), 0.0, GeoDataCoordinates::Radian );
232     GeoDataCoordinates minLatPoint( averageLongitude, minLat(), 0.0, GeoDataCoordinates::Radian );
233 
234     qreal dummyX, dummyY; // not needed
235 
236     if ( latLonAltBox.north() > maxLat() ||
237          screenCoordinates( maxLatPoint, viewport, dummyX, dummyY ) ) {
238         latLonAltBox.setNorth( maxLat() );
239     }
240     if ( latLonAltBox.north() < minLat() ||
241          screenCoordinates( minLatPoint, viewport, dummyX, dummyY ) ) {
242         latLonAltBox.setSouth( minLat() );
243     }
244 
245     latLonAltBox.setMinAltitude(      -100000000.0 );
246     latLonAltBox.setMaxAltitude( 100000000000000.0 );
247 
248     return latLonAltBox;
249 }
250 
251 
mapRegion(const ViewportParams * viewport) const252 QRegion AbstractProjection::mapRegion( const ViewportParams *viewport ) const
253 {
254     return QRegion( mapShape( viewport ).toFillPolygon().toPolygon() );
255 }
256