Marble

GeoDataLatLonBox.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2007 Andrew Manson <[email protected]>
4 // SPDX-FileCopyrightText: 2008-2009 Torsten Rahn <[email protected]>
5 //
6 
7 
8 #include "GeoDataLatLonBox.h"
9 
10 #include "MarbleDebug.h"
11 #include "GeoDataLineString.h"
12 
13 #include "GeoDataTypes.h"
14 
15 #include <QDataStream>
16 
17 namespace Marble
18 {
19 
20 const GeoDataLatLonBox GeoDataLatLonBox::empty = GeoDataLatLonBox();
21 
22 class GeoDataLatLonBoxPrivate
23 {
24  public:
25  GeoDataLatLonBoxPrivate()
26  : m_north( 0.0 ),
27  m_south( 0.0 ),
28  m_east( 0.0 ),
29  m_west( 0.0 ),
30  m_rotation( 0.0 )
31  {
32  }
33 
34  qreal m_north;
35  qreal m_south;
36  qreal m_east;
37  qreal m_west;
38  qreal m_rotation; // NOT implemented yet!
39 };
40 
41 bool operator==( GeoDataLatLonBox const& lhs, GeoDataLatLonBox const& rhs )
42 {
43  return lhs.d->m_west == rhs.d->m_west &&
44  lhs.d->m_east == rhs.d->m_east &&
45  lhs.d->m_north == rhs.d->m_north &&
46  lhs.d->m_south == rhs.d->m_south &&
47  lhs.d->m_rotation == rhs.d->m_rotation;
48 }
49 
50 bool operator!=( GeoDataLatLonBox const& lhs, GeoDataLatLonBox const& rhs )
51 {
52  return !( lhs == rhs );
53 }
54 
55 GeoDataLatLonBox::GeoDataLatLonBox()
56  : GeoDataObject(),
57  d( new GeoDataLatLonBoxPrivate )
58 {
59 }
60 
61 GeoDataLatLonBox::GeoDataLatLonBox( qreal north, qreal south, qreal east, qreal west, GeoDataCoordinates::Unit unit )
62  : GeoDataObject(),
63  d( new GeoDataLatLonBoxPrivate )
64 {
65  setBoundaries( north, south, east, west, unit );
66 }
67 
68 GeoDataLatLonBox::GeoDataLatLonBox( const GeoDataLatLonBox & other )
69  : GeoDataObject( other ),
70  d( new GeoDataLatLonBoxPrivate( *other.d ) )
71 {
72 }
73 
74 GeoDataLatLonBox::~GeoDataLatLonBox()
75 {
76  delete d;
77 }
78 
79 const char* GeoDataLatLonBox::nodeType() const
80 {
81  return GeoDataTypes::GeoDataLatLonBoxType;
82 }
83 
85 {
86  if ( unit == GeoDataCoordinates::Degree ) {
87  return d->m_north * RAD2DEG;
88  }
89  return d->m_north;
90 }
91 
92 void GeoDataLatLonBox::setNorth( const qreal north, GeoDataCoordinates::Unit unit )
93 {
94  switch( unit ){
95  default:
96  case GeoDataCoordinates::Radian:
98  break;
99  case GeoDataCoordinates::Degree:
100  d->m_north = GeoDataCoordinates::normalizeLat( north * DEG2RAD );
101  break;
102  }
103 }
104 
106 {
107  if ( unit == GeoDataCoordinates::Degree ) {
108  return d->m_south * RAD2DEG;
109  }
110  return d->m_south;
111 }
112 
113 void GeoDataLatLonBox::setSouth( const qreal south, GeoDataCoordinates::Unit unit )
114 {
115  switch( unit ){
116  default:
117  case GeoDataCoordinates::Radian:
118  d->m_south = GeoDataCoordinates::normalizeLat( south );
119  break;
120  case GeoDataCoordinates::Degree:
121  d->m_south = GeoDataCoordinates::normalizeLat( south * DEG2RAD );
122  break;
123  }
124 }
125 
127 {
128  if ( unit == GeoDataCoordinates::Degree ) {
129  return d->m_east * RAD2DEG;
130  }
131  return d->m_east;
132 }
133 
134 void GeoDataLatLonBox::setEast( const qreal east, GeoDataCoordinates::Unit unit )
135 {
136  switch( unit ){
137  default:
138  case GeoDataCoordinates::Radian:
139  d->m_east = GeoDataCoordinates::normalizeLon( east );
140  break;
141  case GeoDataCoordinates::Degree:
142  d->m_east = GeoDataCoordinates::normalizeLon( east * DEG2RAD );
143  break;
144  }
145 }
146 
148 {
149  if ( unit == GeoDataCoordinates::Degree ) {
150  return d->m_west * RAD2DEG;
151  }
152  return d->m_west;
153 }
154 
155 void GeoDataLatLonBox::setWest( const qreal west, GeoDataCoordinates::Unit unit )
156 {
157  switch( unit ){
158  default:
159  case GeoDataCoordinates::Radian:
160  d->m_west = GeoDataCoordinates::normalizeLon( west );
161  break;
162  case GeoDataCoordinates::Degree:
163  d->m_west = GeoDataCoordinates::normalizeLon( west * DEG2RAD );
164  break;
165  }
166 }
167 
168 void GeoDataLatLonBox::setRotation( const qreal rotation, GeoDataCoordinates::Unit unit )
169 {
170  switch( unit ){
171  default:
172  case GeoDataCoordinates::Radian:
173  d->m_rotation = rotation;
174  break;
175  case GeoDataCoordinates::Degree:
176  d->m_rotation = rotation * DEG2RAD;
177  break;
178  }
179 }
180 
182 {
183  if ( unit == GeoDataCoordinates::Degree ) {
184  return d->m_rotation * RAD2DEG;
185  }
186  return d->m_rotation;
187 }
188 
189 void GeoDataLatLonBox::boundaries( qreal &north, qreal &south, qreal &east, qreal &west, GeoDataCoordinates::Unit unit ) const
190 {
191  switch( unit ){
192  default:
193  case GeoDataCoordinates::Radian:
194  north = d->m_north;
195  south = d->m_south;
196  east = d->m_east;
197  west = d->m_west;
198  break;
199  case GeoDataCoordinates::Degree:
200  north = d->m_north * RAD2DEG;
201  south = d->m_south * RAD2DEG;
202  east = d->m_east * RAD2DEG;
203  west = d->m_west * RAD2DEG;
204  break;
205  }
206 }
207 
208 void GeoDataLatLonBox::setBoundaries( qreal north, qreal south, qreal east, qreal west, GeoDataCoordinates::Unit unit )
209 {
210  switch( unit ){
211  default:
212  case GeoDataCoordinates::Radian:
213  d->m_north = GeoDataCoordinates::normalizeLat( north );
214  d->m_south = GeoDataCoordinates::normalizeLat( south );
215  d->m_east = GeoDataCoordinates::normalizeLon( east );
216  d->m_west = GeoDataCoordinates::normalizeLon( west );
217  break;
218  case GeoDataCoordinates::Degree:
219  d->m_north = GeoDataCoordinates::normalizeLat( north * DEG2RAD );
220  d->m_south = GeoDataCoordinates::normalizeLat( south * DEG2RAD );
221  d->m_east = GeoDataCoordinates::normalizeLon( east * DEG2RAD );
222  d->m_west = GeoDataCoordinates::normalizeLon( west * DEG2RAD );
223  break;
224  }
225 }
226 
227 void GeoDataLatLonBox::scale(qreal verticalFactor, qreal horizontalFactor) const
228 {
229  GeoDataCoordinates const middle = center();
230  qreal const deltaY = 0.5 * height() * verticalFactor;
231  qreal const deltaX = 0.5 * width() * horizontalFactor;
232  d->m_north = qMin((middle.latitude() + deltaY), static_cast<qreal>(M_PI/2));
233  d->m_south = qMax((middle.latitude() - deltaY), static_cast<qreal>(-M_PI/2));
234  if (deltaX > 180 * DEG2RAD) {
235  d->m_east = M_PI;
236  d->m_west = -M_PI;
237  }
238  else {
239  d->m_east = GeoDataCoordinates::normalizeLon(middle.longitude() + deltaX);
240  d->m_west = GeoDataCoordinates::normalizeLon(middle.longitude() - deltaX);
241  }
242 }
243 
244 GeoDataLatLonBox GeoDataLatLonBox::scaled(qreal verticalFactor, qreal horizontalFactor) const
245 {
246  GeoDataLatLonBox result = *this;
247  result.scale(verticalFactor, horizontalFactor);
248  return result;
249 }
250 
252 {
253  return GeoDataLatLonBox::width( d->m_east, d->m_west, unit );
254 }
255 
256 qreal GeoDataLatLonBox::width( qreal east, qreal west, GeoDataCoordinates::Unit unit )
257 {
258  qreal width = fabs( (qreal)( GeoDataLatLonBox::crossesDateLine(east, west)
259  ? 2 * M_PI - west + east
260  : east - west ) );
261 
262  // This also covers the case where this bounding box covers the whole
263  // longitude range ( -180 <= lon <= + 180 ).
264  if ( width > 2 * M_PI ) {
265  width = 2 * M_PI;
266  }
267 
268  if ( unit == GeoDataCoordinates::Degree ) {
269  return width * RAD2DEG;
270  }
271 
272  return width;
273 }
274 
276 {
277  return GeoDataLatLonBox::height(d->m_north, d->m_south, unit);
278 }
279 
280 qreal GeoDataLatLonBox::height(qreal north, qreal south, GeoDataCoordinates::Unit unit)
281 {
282  qreal height = fabs( (qreal)( south - north ) );
283 
284  if ( unit == GeoDataCoordinates::Degree ) {
285  return height * RAD2DEG;
286  }
287 
288  return height;
289 }
290 
292 {
293  return GeoDataLatLonBox::crossesDateLine(d->m_east, d->m_west);
294 }
295 
296 bool GeoDataLatLonBox::crossesDateLine(qreal east, qreal west)
297 {
298  return east < west || ( east == M_PI && west == -M_PI );
299 }
300 
302 {
303  if( isEmpty() )
304  return GeoDataCoordinates();
305 
306  if( crossesDateLine() )
307  return GeoDataCoordinates( GeoDataCoordinates::normalizeLon( east() + 2 * M_PI - ( east() + 2 * M_PI - west() ) / 2 ) ,
308  north() - ( north() - south() ) / 2 );
309  else
310  return GeoDataCoordinates( east() - ( east() - west() ) / 2,
311  north() - ( north() - south() ) / 2 );
312 }
313 
315 {
316  switch ( pole ) {
317  case NorthPole:
318  return ( 2 * north() == +M_PI );
319  case SouthPole:
320  return ( 2 * south() == -M_PI );
321  default:
322  case AnyPole:
323  return ( 2 * north() == +M_PI
324  || 2 * south() == -M_PI );
325  }
326 
327  mDebug() << Q_FUNC_INFO << "Invalid pole";
328  return false;
329 }
330 
331 bool GeoDataLatLonBox::contains(qreal lon, qreal lat) const
332 {
333  if ( lat < d->m_south || lat > d->m_north ) {
334  return false;
335  }
336 
337  // We need to take care of the normal case ...
338  if ( ( ( lon < d->m_west || lon > d->m_east ) && ( d->m_west < d->m_east ) ) ||
339  // ... and the case where the bounding box crosses the date line:
340  ( ( lon < d->m_west && lon > d->m_east ) && ( d->m_west > d->m_east ) ) )
341  return false;
342 
343  return true;
344 }
345 
346 bool GeoDataLatLonBox::contains( const GeoDataCoordinates &point ) const
347 {
348  qreal lon, lat;
349 
350  point.geoCoordinates( lon, lat );
351 
352  return contains(lon, lat);
353 }
354 
355 bool GeoDataLatLonBox::contains( const GeoDataLatLonBox &other ) const
356 {
357  // check the contain criterion for the latitude first as this is trivial:
358 
359  if ( d->m_north >= other.north() && d->m_south <= other.south() ) {
360 
361  if ( !crossesDateLine() ) {
362  if ( !other.crossesDateLine() ) {
363  // "Normal" case: both bounding boxes don't cross the date line
364  if ( d->m_west <= other.west() && d->m_east >= other.east() ) {
365  return true;
366  }
367  }
368  else {
369  // The other bounding box crosses the date line, "this" one does not:
370  // So the date line splits the other bounding box in two parts.
371  // Hence "this" bounding box could be fully contained by one of them.
372  // So for both cases we are able to ignore the "overhanging" portion
373  // and thereby basically reduce the problem to the "normal" case:
374 
375  if ( ( other.west() <= d->m_west && d->m_east <= +M_PI )
376  || ( other.east() >= d->m_east && d->m_west >= -M_PI ) ) {
377  return true;
378  }
379  }
380  }
381  else {
382  if ( other.crossesDateLine() ) {
383  // Other "Simple" case: both bounding boxes cross the date line
384  if ( d->m_west <= other.west() && d->m_east >= other.east() ) {
385  return true;
386  }
387  }
388  else {
389  // "This" bounding box crosses the date line, the other one does not.
390  // So the date line splits "this" bounding box in two parts.
391  // Hence the other bounding box could be fully contained by one of them.
392  // So for both cases we are able to ignore the "overhanging" portion
393  // and thereby basically reduce the problem to the "normal" case:
394 
395  if ( ( d->m_west <= other.west() && other.east() <= +M_PI )
396  || ( d->m_east >= other.east() && other.west() >= -M_PI ) ) {
397  return true;
398  }
399 
400  // if this bounding box covers the whole longitude range ( -180 <= lon <= + 180 )
401  // then of course the "inner" bounding box is "inside"
402  if ( d->m_west == -M_PI && d->m_east == +M_PI ) {
403  return true;
404  }
405  }
406 
407  }
408  }
409 
410  return false;
411 }
412 
413 bool GeoDataLatLonBox::intersects( const GeoDataLatLonBox &other ) const
414 {
415  if ( isEmpty() || other.isEmpty() ) {
416  return false;
417  }
418 
419  // check the intersection criterion for the latitude first:
420 
421  // Case 1: northern boundary of other box intersects:
422  if ( (d->m_north >= other.d->m_north && d->m_south <= other.d->m_north)
423  // Case 2: northern boundary of this box intersects:
424  || (other.d->m_north >= d->m_north && other.d->m_south <= d->m_north)
425  // Case 3: southern boundary of other box intersects:
426  || (d->m_north >= other.d->m_south && d->m_south <= other.d->m_south)
427  // Case 4: southern boundary of this box intersects:
428  || (other.d->m_north >= d->m_south && other.d->m_south <= d->m_south)) {
429 
430  if ( !crossesDateLine() ) {
431  if ( !other.crossesDateLine() ) {
432  // "Normal" case: both bounding boxes don't cross the date line
433  // Case 1: eastern boundary of other box intersects:
434  if ( (d->m_east >= other.d->m_east && d->m_west <= other.d->m_east)
435  // Case 2: eastern boundary of this box intersects:
436  || (other.d->m_east >= d->m_east && other.d->m_west <= d->m_east)
437  // Case 3: western boundary of other box intersects:
438  || (d->m_east >= other.d->m_west && d->m_west <= other.d->m_west)
439  // Case 4: western boundary of this box intersects:
440  || (other.d->m_east >= d->m_west && other.d->m_west <= d->m_west)) {
441  return true;
442  }
443  }
444  else {
445  // The other bounding box crosses the date line, "this" one does not:
446  // So the date line splits the other bounding box in two parts.
447 
448  if ( d->m_west <= other.d->m_east || d->m_east >= other.d->m_west) {
449  return true;
450  }
451  }
452  }
453  else {
454  if ( other.crossesDateLine() ) {
455  // The trivial case: both bounding boxes cross the date line and intersect
456  return true;
457  }
458  else {
459  // "This" bounding box crosses the date line, the other one does not.
460  // So the date line splits "this" bounding box in two parts.
461  //
462  // This also covers the case where this bounding box covers the whole
463  // longitude range ( -180 <= lon <= + 180 ).
464  if ( other.d->m_west <= d->m_east || other.d->m_east >= d->m_west ) {
465  return true;
466  }
467  }
468  }
469  }
470 
471  return false;
472 }
473 
475 {
476  if ( isEmpty() ) {
477  return other;
478  }
479 
480  if ( other.isEmpty() ) {
481  return *this;
482  }
483 
484  GeoDataLatLonBox result;
485 
486  // use the position of the centers of the boxes to determine the "smallest"
487  // box (i.e. should the total box go through IDL or not). this
488  // determination does not depend on one box or the other crossing IDL too
489  GeoDataCoordinates c1 = center();
490  GeoDataCoordinates c2 = other.center();
491 
492  // do latitude first, quite simple
493  result.setNorth(qMax( d->m_north, other.north() ) );
494  result.setSouth( qMin( d->m_south, other.south() ) );
495 
496  qreal w1 = d->m_west;
497  qreal w2 = other.west();
498  qreal e1 = d->m_east;
499  qreal e2 = other.east();
500 
501  bool const idl1 = d->m_east < d->m_west;
502  bool const idl2 = other.d->m_east < other.d->m_west;
503 
504  if ( idl1 ) {
505  w1 += 2* M_PI;
506  e1 += 2* M_PI;
507  }
508  if ( idl2 ) {
509  w2 += 2* M_PI;
510  e2 += 2* M_PI;
511  }
512 
513  // in the usual case, we take the maximum of east bounds, and
514  // the minimum of west bounds. The exceptions are:
515  // - centers of boxes are more than 180 apart
516  // (so the smallest box should go around the IDL)
517  //
518  // - 1 but not 2 boxes are crossing IDL
519  if ( fabs( c2.longitude()-c1.longitude() ) > M_PI
520  || ( idl1 ^ idl2 ) ) {
521  // exceptions, we go the unusual way:
522  // min of east, max of west
523  result.setEast( qMin( e1, e2 ) );
524  result.setWest( qMax( w1, w2 ) );
525  }
526  else {
527  // normal case, max of east, min of west
528  result.setEast( qMax( e1, e2 ) );
529  result.setWest( qMin( w1, w2 ) );
530  }
531  return result;
532 }
533 
535 {
536  QVector<GeoDataCoordinates> coordinates;
537  coordinates.reserve(4);
538 
539  coordinates.append( GeoDataCoordinates( west(), north() ) );
540  coordinates.append( GeoDataCoordinates( west(), south() ) );
541  coordinates.append( GeoDataCoordinates( east() + ( crossesDateLine() ? 2 * M_PI : 0 ), north() ) );
542  coordinates.append( GeoDataCoordinates( east() + ( crossesDateLine() ? 2 * M_PI : 0 ), south() ) );
543 
544  const qreal cosRotation = cos( rotation() );
545  const qreal sinRotation = sin( rotation() );
546 
547  qreal centerLat = center().latitude();
548  qreal centerLon = center().longitude();
549  if ( GeoDataLatLonBox( 0, 0, center().longitude(), west() ).crossesDateLine() ) {
550  if ( !centerLon ) centerLon += M_PI;
551  else centerLon += 2 * M_PI;
552  }
553 
554  GeoDataLatLonBox box;
555 
556  bool northSet = false;
557  bool southSet = false;
558  bool eastSet = false;
559  bool westSet = false;
560 
561  for ( const GeoDataCoordinates& coord: coordinates ) {
562 
563  const qreal lon = coord.longitude();
564  const qreal lat = coord.latitude();
565 
566  const qreal rotatedLon = ( lon - centerLon ) * cosRotation - ( lat - centerLat ) * sinRotation + centerLon;
567  const qreal rotatedLat = ( lon - centerLon ) * sinRotation + ( lat - centerLat ) * cosRotation + centerLat;
568 
569  if ( !northSet || rotatedLat > box.north() ) {
570  northSet = true;
571  box.setNorth( rotatedLat );
572  }
573 
574  if ( !southSet || rotatedLat < box.south() ) {
575  southSet = true;
576  box.setSouth( rotatedLat );
577  }
578 
579  if ( !westSet || rotatedLon < box.west() ) {
580  westSet = true;
581  box.setWest( rotatedLon );
582  }
583 
584  if ( !eastSet || rotatedLon > box.east() ) {
585  eastSet = true;
586  box.setEast( rotatedLon );
587  }
588  }
589 
590  box.setBoundaries(box.north(), box.south(), box.east(), box.west());
591 
592  return box;
593 }
594 
595 GeoDataLatLonBox& GeoDataLatLonBox::operator=( const GeoDataLatLonBox &other )
596 {
597  GeoDataObject::operator=( other );
598 
599  *d = *other.d;
600  return *this;
601 }
602 
603 GeoDataLatLonBox GeoDataLatLonBox::operator|( const GeoDataLatLonBox& other ) const
604 {
605  return united( other );
606 }
607 
609 {
610  *this = united( other );
611  return *this;
612 }
613 
614 
615 void GeoDataLatLonBox::pack( QDataStream& stream ) const
616 {
617  GeoDataObject::pack( stream );
618 
619  stream << d->m_north << d->m_south << d->m_east << d->m_west << d->m_rotation;
620 }
621 
623 {
624  GeoDataObject::unpack( stream );
625 
626  stream >> d->m_north >> d->m_south >> d->m_east >> d->m_west >> d->m_rotation;
627 }
628 
630 {
631  // If the line string is empty return an empty boundingbox
632  if ( lineString.isEmpty() ) {
633  return GeoDataLatLonBox();
634  }
635 
636  qreal lon, lat;
637  lineString.first().geoCoordinates( lon, lat );
639 
640  qreal north = lat;
641  qreal south = lat;
642  qreal west = lon;
643  qreal east = lon;
644 
645  // If there's only a single node stored then the boundingbox only contains that point
646  if ( lineString.size() == 1 )
647  return GeoDataLatLonBox( north, south, east, west );
648 
649  // Specifies whether the polygon crosses the IDL
650  bool idlCrossed = false;
651 
652  // "idlCrossState" specifies the state concerning IDL crossage.
653  // This is needed in order to create optimal bounding boxes in case of covering the IDL
654  // Every time the IDL gets crossed from east to west the idlCrossState value gets
655  // increased by one.
656  // Every time the IDL gets crossed from west to east the idlCrossState value gets
657  // decreased by one.
658 
659  int idlCrossState = 0;
660  int idlMaxCrossState = 0;
661  int idlMinCrossState = 0;
662 
663  // Holds values for east and west while idlCrossState != 0
664  qreal otherWest = lon;
665  qreal otherEast = lon;
666 
667  qreal previousLon = lon;
668 
669  int currentSign = ( lon < 0 ) ? -1 : +1;
670  int previousSign = currentSign;
671 
674 
675  bool processingLastNode = false;
676 
677  while( it != itEnd ) {
678  // Get coordinates and normalize them to the desired range.
679  (it)->geoCoordinates( lon, lat );
681 
682  // Determining the maximum and minimum latitude
683  if ( lat > north ) {
684  north = lat;
685  } else if ( lat < south ) {
686  south = lat;
687  }
688 
689  currentSign = ( lon < 0 ) ? -1 : +1;
690 
691  // Once the polyline crosses the dateline the covered bounding box
692  // would cover the whole [-M_PI; M_PI] range.
693  // When looking separately at the longitude range that gets covered
694  // east and west from the IDL we get two bounding boxes (we prefix
695  // the resulting longitude range on the "other side" with "other").
696  // By picking the "inner" range values we get a more appropriate
697  // optimized single bounding box.
698 
699  // IDL check
700  if ( previousSign != currentSign
701  && fabs( previousLon ) + fabs( lon ) > M_PI ) {
702 
703  // Initialize values for otherWest and otherEast
704  if ( idlCrossed == false ) {
705  otherWest = lon;
706  otherEast = lon;
707  idlCrossed = true;
708  }
709 
710  // Determine the new IDL Cross State
711  if ( previousLon < 0 ) {
712  idlCrossState++;
713  if ( idlCrossState > idlMaxCrossState ) {
714  idlMaxCrossState = idlCrossState;
715  }
716  }
717  else {
718  idlCrossState--;
719  if ( idlCrossState < idlMinCrossState ) {
720  idlMinCrossState = idlCrossState;
721  }
722  }
723  }
724 
725  if ( idlCrossState == 0 ) {
726  if ( lon > east ) east = lon;
727  if ( lon < west ) west = lon;
728  }
729  else {
730  if ( lon > otherEast ) otherEast = lon;
731  if ( lon < otherWest ) otherWest = lon;
732  }
733 
734  previousLon = lon;
735  previousSign = currentSign;
736 
737  if ( processingLastNode ) {
738  break;
739  }
740  ++it;
741 
742  if( lineString.isClosed() && it == itEnd ) {
743  it = lineString.constBegin();
744  processingLastNode = true;
745  }
746  }
747 
748  if ( idlCrossed ) {
749  if ( idlMinCrossState < 0 ) {
750  east = otherEast;
751  }
752  if ( idlMaxCrossState > 0 ) {
753  west = otherWest;
754  }
755  if ( ( idlMinCrossState < 0 && idlMaxCrossState > 0 )
756  || idlMinCrossState < -1 || idlMaxCrossState > 1
757  || west <= east ) {
758  east = +M_PI;
759  west = -M_PI;
760  // if polygon fully in south hemisphere, contain south pole
761  if( north < 0 ) {
762  south = -M_PI/2;
763  } else {
764  north = M_PI/2;
765  }
766  }
767  }
768 
769  return GeoDataLatLonBox( north, south, east, west );
770 }
771 
773 {
774  return d->m_north == d->m_south && d->m_east == d->m_west;
775 }
776 
778 {
779  return *this == empty;
780 }
781 
783  const GeoDataLatLonBox& rhs,
784  const qreal factor)
785 {
786  bool equal = true;
787 
788  // Check the latitude for approximate equality
789 
790  double latDelta = lhs.height() * factor;
791 
792  if (fabs(lhs.north() - rhs.north()) > latDelta) equal = false;
793  if (fabs(lhs.south() - rhs.south()) > latDelta) equal = false;
794 
795 
796  // Check the longitude for approximate equality
797 
798  double lonDelta = lhs.width() * factor;
799 
800  double lhsEast = lhs.east();
801  double rhsEast = rhs.east();
802 
803  if (!GeoDataLatLonBox::crossesDateLine(lhsEast, rhsEast)) {
804  if (fabs(lhsEast - rhsEast) > lonDelta) equal = false;
805  }
806  else {
807  if (lhsEast < 0 && rhsEast > 0) {
808  lhsEast += 2 * M_PI;
809  if (fabs(lhsEast - rhsEast) > lonDelta) equal = false;
810  }
811  if (lhsEast > 0 && rhsEast < 0) {
812  rhsEast += 2 * M_PI;
813  if (fabs(lhsEast - rhsEast) > lonDelta) equal = false;
814  }
815  }
816 
817  double lhsWest = lhs.west();
818  double rhsWest = rhs.west();
819 
820  if (!GeoDataLatLonBox::crossesDateLine(lhsWest, rhsWest)) {
821  if (fabs(lhsWest - rhsWest) > lonDelta) equal = false;
822  }
823  else {
824  if (lhsWest < 0 && rhsWest > 0) {
825  lhsWest += 2 * M_PI;
826  if (fabs(lhsWest - rhsWest) > lonDelta) equal = false;
827  }
828  if (lhsWest > 0 && rhsWest < 0) {
829  rhsWest += 2 * M_PI;
830  if (fabs(lhsWest - rhsWest) > lonDelta) equal = false;
831  }
832  }
833 
834  return equal;
835 }
836 
837 
839 {
840  *this = empty;
841 }
842 }
bool crossesDateLine() const
Detect whether the bounding box crosses the IDL.
GeoDataLatLonBox & operator|=(const GeoDataLatLonBox &other)
Unites this bounding box with the given one.
A 3d point representation.
qreal height(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the height of the latitude interval.
virtual GeoDataCoordinates center() const
returns the center of this box
@ AnyPole
Any pole.
Definition: MarbleGlobal.h:140
void geoCoordinates(qreal &lon, qreal &lat, GeoDataCoordinates::Unit unit) const
use this function to get the longitude and latitude with one call - use the unit parameter to switch ...
qreal east(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the eastern boundary of the bounding box.
virtual bool isNull() const
Indicates whether the bounding box only contains a single 2D point ("singularity").
void append(const T &value)
@ SouthPole
Only South Pole.
Definition: MarbleGlobal.h:142
qreal longitude(GeoDataCoordinates::Unit unit) const
retrieves the longitude of the GeoDataCoordinates object use the unit parameter to switch between Rad...
qreal north(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the northern boundary of the bounding box.
QVector< GeoDataCoordinates >::ConstIterator constBegin() const
Returns a const iterator that points to the begin of the LineString.
bool operator==(const Qt3DRender::QGraphicsApiFilter &reference, const Qt3DRender::QGraphicsApiFilter &sample)
A LineString that allows to store a contiguous set of line segments.
virtual bool isEmpty() const
Indicates whether the bounding box is not initialised (and contains nothing).
qreal width(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the width of the longitude interval.
qreal west(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the western boundary of the bounding box.
qreal latitude(GeoDataCoordinates::Unit unit) const
retrieves the latitude of the GeoDataCoordinates object use the unit parameter to switch between Radi...
bool isEmpty() const
Returns whether the LineString has no nodes at all.
static bool fuzzyCompare(const GeoDataLatLonBox &lhs, const GeoDataLatLonBox &rhs, const qreal factor=0.01)
Indicates whether two bounding boxes are roughly equal.
void unpack(QDataStream &stream) override
Unserialize the contents of the feature from stream.
QVector< GeoDataCoordinates >::ConstIterator constEnd() const
Returns a const iterator that points to the end of the LineString.
void pack(QDataStream &stream) const override
Serialize the contents of the feature to stream.
bool operator!=(const Qt3DRender::QGraphicsApiFilter &reference, const Qt3DRender::QGraphicsApiFilter &sample)
A class that defines a 2D bounding box for geographic data.
void pack(QDataStream &stream) const override
Reimplemented from Serializable.
Binds a QML item to a specific geodetic location in screen coordinates.
GeoDataCoordinates & first()
Returns a reference to the first node in the LineString. This method detaches the returned coordinate...
const char * nodeType() const override
Provides type information for downcasting a GeoData.
GeoDataLatLonBox united(const GeoDataLatLonBox &other) const
Returns the bounding LatLonBox of this box with the given one.
void reserve(int size)
qreal rotation(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the rotation of the bounding box.
qreal south(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the southern boundary of the bounding box.
GeoDataLatLonBox toCircumscribedRectangle() const
Unit
enum used constructor to specify the units used
@ NorthPole
Only North Pole.
Definition: MarbleGlobal.h:141
static GeoDataLatLonBox fromLineString(const GeoDataLineString &lineString)
Create the smallest bounding box from a line string.
bool containsPole(Pole pole=AnyPole) const
Detect whether the bounding box contains one of the poles.
static qreal normalizeLat(qreal lat, GeoDataCoordinates::Unit=GeoDataCoordinates::Radian)
normalize latitude to always be in -M_PI / 2.
virtual bool isClosed() const
Returns whether a LineString is a closed polygon.
void unpack(QDataStream &steam) override
Reimplemented from Serializable.
void scale(qreal verticalFactor, qreal horizontalFactor) const
Changes the differences between the boundaries and the center by the given factor,...
static void normalizeLonLat(qreal &lon, qreal &lat, GeoDataCoordinates::Unit=GeoDataCoordinates::Radian)
normalize both longitude and latitude at the same time This method normalizes both latitude and longi...
static qreal normalizeLon(qreal lon, GeoDataCoordinates::Unit=GeoDataCoordinates::Radian)
normalize the longitude to always be -M_PI <= lon <= +M_PI (Radian).
int size() const
Returns the number of nodes in a LineString.
QDebug mDebug()
a function to replace qDebug() in Marble library code
Definition: MarbleDebug.cpp:31
virtual void clear()
Resets the bounding box to its uninitialised state (and thus contains nothing).
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Jul 1 2022 05:33:22 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.