File indexing completed on 2024-05-12 03:50:13

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2007 Andrew Manson <g.real.ate@gmail.com>
0004 // SPDX-FileCopyrightText: 2008-2009 Torsten Rahn <rahn@kde.org>
0005 //
0006 
0007 
0008 #include "GeoDataLatLonBox.h"
0009 
0010 #include "MarbleDebug.h"
0011 #include "GeoDataLineString.h"
0012 
0013 #include "GeoDataTypes.h"
0014 
0015 #include <QDataStream>
0016 
0017 namespace Marble
0018 {
0019 
0020 const GeoDataLatLonBox GeoDataLatLonBox::empty = GeoDataLatLonBox();
0021 
0022 class GeoDataLatLonBoxPrivate
0023 {
0024  public:
0025     GeoDataLatLonBoxPrivate()
0026         : m_north( 0.0 ),
0027           m_south( 0.0 ),
0028           m_east( 0.0 ),
0029           m_west( 0.0 ),
0030           m_rotation( 0.0 )
0031     {
0032     }
0033 
0034     qreal m_north;
0035     qreal m_south;
0036     qreal m_east;
0037     qreal m_west;
0038     qreal m_rotation; // NOT implemented yet!
0039 };
0040 
0041 bool operator==( GeoDataLatLonBox const& lhs, GeoDataLatLonBox const& rhs )
0042 {
0043     return lhs.d->m_west == rhs.d->m_west &&
0044            lhs.d->m_east == rhs.d->m_east &&
0045            lhs.d->m_north == rhs.d->m_north &&
0046            lhs.d->m_south == rhs.d->m_south &&
0047            lhs.d->m_rotation == rhs.d->m_rotation;
0048 }
0049 
0050 bool operator!=( GeoDataLatLonBox const& lhs, GeoDataLatLonBox const& rhs )
0051 {
0052     return !( lhs == rhs );
0053 }
0054 
0055 GeoDataLatLonBox::GeoDataLatLonBox()
0056     : GeoDataObject(),
0057       d( new GeoDataLatLonBoxPrivate )
0058 {
0059 }
0060 
0061 GeoDataLatLonBox::GeoDataLatLonBox( qreal north, qreal south, qreal east, qreal west, GeoDataCoordinates::Unit unit )
0062     : GeoDataObject(),
0063       d( new GeoDataLatLonBoxPrivate )
0064 {
0065     setBoundaries( north, south, east, west, unit );
0066 }
0067 
0068 GeoDataLatLonBox::GeoDataLatLonBox( const GeoDataLatLonBox & other )
0069     : GeoDataObject( other ),
0070       d( new GeoDataLatLonBoxPrivate( *other.d ) )
0071 {
0072 }
0073 
0074 GeoDataLatLonBox::~GeoDataLatLonBox()
0075 {
0076     delete d;
0077 }
0078 
0079 const char* GeoDataLatLonBox::nodeType() const
0080 {
0081     return GeoDataTypes::GeoDataLatLonBoxType;
0082 }
0083 
0084 qreal GeoDataLatLonBox::north( GeoDataCoordinates::Unit unit ) const
0085 {
0086     if ( unit == GeoDataCoordinates::Degree ) {
0087         return d->m_north * RAD2DEG;
0088     }
0089     return d->m_north;
0090 }
0091 
0092 void GeoDataLatLonBox::setNorth( const qreal north, GeoDataCoordinates::Unit unit )
0093 {
0094     switch( unit ){
0095     default:
0096     case GeoDataCoordinates::Radian:
0097         d->m_north = GeoDataCoordinates::normalizeLat( north );
0098         break;
0099     case GeoDataCoordinates::Degree:
0100         d->m_north = GeoDataCoordinates::normalizeLat( north * DEG2RAD );
0101         break;
0102     }
0103 }
0104 
0105 qreal GeoDataLatLonBox::south( GeoDataCoordinates::Unit unit ) const
0106 {
0107     if ( unit == GeoDataCoordinates::Degree ) {
0108         return d->m_south * RAD2DEG;
0109     }
0110     return d->m_south;
0111 }
0112 
0113 void GeoDataLatLonBox::setSouth( const qreal south, GeoDataCoordinates::Unit unit )
0114 {
0115     switch( unit ){
0116     default:
0117     case GeoDataCoordinates::Radian:
0118         d->m_south = GeoDataCoordinates::normalizeLat( south );
0119         break;
0120     case GeoDataCoordinates::Degree:
0121         d->m_south = GeoDataCoordinates::normalizeLat( south * DEG2RAD );
0122         break;
0123     }
0124 }
0125 
0126 qreal GeoDataLatLonBox::east( GeoDataCoordinates::Unit unit ) const
0127 {
0128     if ( unit == GeoDataCoordinates::Degree ) {
0129         return d->m_east * RAD2DEG;
0130     }
0131     return d->m_east;
0132 }
0133 
0134 void GeoDataLatLonBox::setEast( const qreal east, GeoDataCoordinates::Unit unit )
0135 {
0136     switch( unit ){
0137     default:
0138     case GeoDataCoordinates::Radian:
0139         d->m_east = GeoDataCoordinates::normalizeLon( east );
0140         break;
0141     case GeoDataCoordinates::Degree:
0142         d->m_east = GeoDataCoordinates::normalizeLon( east * DEG2RAD );
0143         break;
0144     }
0145 }
0146 
0147 qreal GeoDataLatLonBox::west( GeoDataCoordinates::Unit unit ) const
0148 {
0149     if ( unit == GeoDataCoordinates::Degree ) {
0150         return d->m_west * RAD2DEG;
0151     }
0152     return d->m_west;
0153 }
0154 
0155 void GeoDataLatLonBox::setWest( const qreal west, GeoDataCoordinates::Unit unit )
0156 {
0157     switch( unit ){
0158     default:
0159     case GeoDataCoordinates::Radian:
0160         d->m_west = GeoDataCoordinates::normalizeLon( west );
0161         break;
0162     case GeoDataCoordinates::Degree:
0163         d->m_west = GeoDataCoordinates::normalizeLon( west * DEG2RAD );
0164         break;
0165     }
0166 }
0167 
0168 void GeoDataLatLonBox::setRotation( const qreal rotation, GeoDataCoordinates::Unit unit )
0169 {
0170     switch( unit ){
0171     default:
0172     case GeoDataCoordinates::Radian:
0173         d->m_rotation = rotation;
0174         break;
0175     case GeoDataCoordinates::Degree:
0176         d->m_rotation = rotation * DEG2RAD;
0177         break;
0178     }
0179 }
0180 
0181 qreal GeoDataLatLonBox::rotation( GeoDataCoordinates::Unit unit ) const
0182 {
0183     if ( unit == GeoDataCoordinates::Degree ) {
0184         return d->m_rotation * RAD2DEG;
0185     }
0186     return d->m_rotation;
0187 }
0188 
0189 void GeoDataLatLonBox::boundaries( qreal &north, qreal &south, qreal &east, qreal &west, GeoDataCoordinates::Unit unit ) const
0190 {
0191     switch( unit ){
0192     default:
0193     case GeoDataCoordinates::Radian:
0194         north = d->m_north;
0195         south = d->m_south;
0196         east  = d->m_east;
0197         west  = d->m_west;
0198         break;
0199     case GeoDataCoordinates::Degree:
0200         north = d->m_north * RAD2DEG;
0201         south = d->m_south * RAD2DEG;
0202         east  = d->m_east  * RAD2DEG;
0203         west  = d->m_west  * RAD2DEG;
0204         break;
0205     }
0206 }
0207 
0208 void GeoDataLatLonBox::setBoundaries( qreal north, qreal south, qreal east, qreal west, GeoDataCoordinates::Unit unit )
0209 {
0210     switch( unit ){
0211     default:
0212     case GeoDataCoordinates::Radian:
0213         d->m_north = GeoDataCoordinates::normalizeLat( north );
0214         d->m_south = GeoDataCoordinates::normalizeLat( south );
0215         d->m_east =  GeoDataCoordinates::normalizeLon( east );
0216         d->m_west =  GeoDataCoordinates::normalizeLon( west );
0217         break;
0218     case GeoDataCoordinates::Degree:
0219         d->m_north = GeoDataCoordinates::normalizeLat( north * DEG2RAD );
0220         d->m_south = GeoDataCoordinates::normalizeLat( south * DEG2RAD );
0221         d->m_east =  GeoDataCoordinates::normalizeLon( east * DEG2RAD );
0222         d->m_west =  GeoDataCoordinates::normalizeLon( west * DEG2RAD );
0223         break;
0224     }
0225 }
0226 
0227 void GeoDataLatLonBox::scale(qreal verticalFactor, qreal horizontalFactor) const
0228 {
0229     GeoDataCoordinates const middle = center();
0230     qreal const deltaY = 0.5 * height() * verticalFactor;
0231     qreal const deltaX = 0.5 * width() * horizontalFactor;
0232     d->m_north = qMin((middle.latitude() + deltaY), static_cast<qreal>(M_PI/2));
0233     d->m_south = qMax((middle.latitude() - deltaY), static_cast<qreal>(-M_PI/2));
0234     if (deltaX > 180 * DEG2RAD) {
0235         d->m_east = M_PI;
0236         d->m_west = -M_PI;
0237     }
0238     else {
0239         d->m_east = GeoDataCoordinates::normalizeLon(middle.longitude() + deltaX);
0240         d->m_west = GeoDataCoordinates::normalizeLon(middle.longitude() - deltaX);
0241     }
0242 }
0243 
0244 GeoDataLatLonBox GeoDataLatLonBox::scaled(qreal verticalFactor, qreal horizontalFactor) const
0245 {
0246     GeoDataLatLonBox result = *this;
0247     result.scale(verticalFactor, horizontalFactor);
0248     return result;
0249 }
0250 
0251 qreal GeoDataLatLonBox::width( GeoDataCoordinates::Unit unit ) const
0252 {
0253     return GeoDataLatLonBox::width( d->m_east, d->m_west, unit );
0254 }
0255 
0256 qreal GeoDataLatLonBox::width( qreal east, qreal west, GeoDataCoordinates::Unit unit )
0257 {
0258     qreal width = fabs( (qreal)( GeoDataLatLonBox::crossesDateLine(east, west)
0259                                      ? 2 * M_PI - west + east
0260                                      : east - west ) );
0261 
0262     // This also covers the case where this bounding box covers the whole
0263     // longitude range ( -180 <= lon <= + 180 ).
0264     if ( width > 2 * M_PI ) {
0265         width = 2 * M_PI;
0266     }
0267 
0268     if ( unit == GeoDataCoordinates::Degree ) {
0269         return width * RAD2DEG;
0270     }
0271 
0272     return width;
0273 }
0274 
0275 qreal GeoDataLatLonBox::height( GeoDataCoordinates::Unit unit ) const
0276 {
0277     return GeoDataLatLonBox::height(d->m_north, d->m_south, unit);
0278 }
0279 
0280 qreal GeoDataLatLonBox::height(qreal north, qreal south, GeoDataCoordinates::Unit unit)
0281 {
0282     qreal height = fabs( (qreal)( south - north ) );
0283 
0284     if ( unit == GeoDataCoordinates::Degree ) {
0285         return height * RAD2DEG;
0286     }
0287 
0288     return height;
0289 }
0290 
0291 bool GeoDataLatLonBox::crossesDateLine() const
0292 {
0293     return GeoDataLatLonBox::crossesDateLine(d->m_east, d->m_west);
0294 }
0295 
0296 bool GeoDataLatLonBox::crossesDateLine(qreal east, qreal west)
0297 {
0298     return east < west || ( east == M_PI && west == -M_PI );
0299 }
0300 
0301 GeoDataCoordinates GeoDataLatLonBox::center() const
0302 {
0303     if( isEmpty() )
0304         return GeoDataCoordinates();
0305 
0306     if( crossesDateLine() )
0307         return GeoDataCoordinates( GeoDataCoordinates::normalizeLon( east() + 2 * M_PI - ( east() + 2 * M_PI - west() ) / 2 ) ,
0308                                 north() - ( north() - south() ) / 2 );
0309     else
0310         return GeoDataCoordinates( east() - ( east() - west() ) / 2,
0311                                 north() - ( north() - south() ) / 2 );
0312 }
0313 
0314 bool GeoDataLatLonBox::containsPole( Pole pole ) const
0315 {
0316     switch ( pole ) {
0317       case NorthPole:
0318         return ( 2 * north() == +M_PI );
0319       case SouthPole:
0320         return ( 2 * south() == -M_PI );
0321       default:
0322       case AnyPole:
0323         return (    2 * north() == +M_PI
0324                  || 2 * south() == -M_PI );
0325     }
0326 
0327     mDebug() << "Invalid pole";
0328     return false;
0329 }
0330 
0331 bool GeoDataLatLonBox::contains(qreal lon, qreal lat) const
0332 {
0333     if ( lat < d->m_south || lat > d->m_north ) {
0334         return false;
0335     }
0336 
0337     // We need to take care of the normal case ...
0338     if ( ( ( lon < d->m_west || lon > d->m_east ) && ( d->m_west < d->m_east ) ) ||
0339     // ... and the case where the bounding box crosses the date line:
0340          ( ( lon < d->m_west && lon > d->m_east ) && ( d->m_west > d->m_east ) ) )
0341         return false;
0342 
0343     return true;
0344 }
0345 
0346 bool GeoDataLatLonBox::contains( const GeoDataCoordinates &point ) const
0347 {
0348     qreal lon, lat;
0349 
0350     point.geoCoordinates( lon, lat );
0351 
0352     return contains(lon, lat);
0353 }
0354 
0355 bool GeoDataLatLonBox::contains( const GeoDataLatLonBox &other ) const
0356 {
0357     // check the contain criterion for the latitude first as this is trivial:
0358 
0359     if ( d->m_north >= other.north() && d->m_south <= other.south() ) {
0360 
0361         if ( !crossesDateLine() ) {
0362             if ( !other.crossesDateLine() ) {
0363                 // "Normal" case: both bounding boxes don't cross the date line
0364                 if ( d->m_west <= other.west() && d->m_east >= other.east() ) {
0365                     return true;
0366                 }                
0367             }
0368             else {
0369                 // The other bounding box crosses the date line, "this" one does not:
0370                 // So the date line splits the other bounding box in two parts.
0371                 // Hence "this" bounding box could be fully contained by one of them. 
0372                 // So for both cases we are able to ignore the "overhanging" portion 
0373                 // and thereby basically reduce the problem to the "normal" case: 
0374 
0375                 if ( ( other.west() <= d->m_west && d->m_east <= +M_PI )
0376                   || ( other.east() >= d->m_east && d->m_west >= -M_PI ) ) {
0377                     return true;
0378                 }
0379             }
0380         }
0381         else {
0382             if ( other.crossesDateLine() ) {
0383                 // Other "Simple" case: both bounding boxes cross the date line
0384                 if ( d->m_west <= other.west() && d->m_east >= other.east() ) {
0385                     return true;
0386                 }                
0387             }
0388             else {
0389                 // "This" bounding box crosses the date line, the other one does not.
0390                 // So the date line splits "this" bounding box in two parts.
0391                 // Hence the other bounding box could be fully contained by one of them. 
0392                 // So for both cases we are able to ignore the "overhanging" portion 
0393                 // and thereby basically reduce the problem to the "normal" case: 
0394 
0395                 if ( ( d->m_west <= other.west() && other.east() <= +M_PI )
0396                   || ( d->m_east >= other.east() && other.west() >= -M_PI ) ) {
0397                     return true;
0398                 }
0399 
0400                 // if this bounding box covers the whole longitude range  ( -180 <= lon <= + 180 )
0401                 // then of course the "inner" bounding box is "inside"
0402                 if ( d->m_west == -M_PI && d->m_east == +M_PI ) {
0403                     return true;
0404                 }
0405             }
0406 
0407         }
0408     }
0409 
0410     return false;
0411 }
0412 
0413 bool GeoDataLatLonBox::intersects( const GeoDataLatLonBox &other ) const
0414 {
0415     if ( isEmpty() || other.isEmpty() ) {
0416         return false;
0417     }
0418 
0419     // check the intersection criterion for the latitude first:
0420 
0421     // Case 1: northern boundary of other box intersects:
0422     if (   (d->m_north >= other.d->m_north && d->m_south <= other.d->m_north)
0423            // Case 2: northern boundary of this box intersects:
0424            || (other.d->m_north >= d->m_north && other.d->m_south <= d->m_north)
0425            // Case 3: southern boundary of other box intersects:
0426            || (d->m_north >= other.d->m_south && d->m_south <= other.d->m_south)
0427            // Case 4: southern boundary of this box intersects:
0428            || (other.d->m_north >= d->m_south && other.d->m_south <= d->m_south)) {
0429 
0430         if ( !crossesDateLine() ) {
0431             if ( !other.crossesDateLine() ) {
0432                 // "Normal" case: both bounding boxes don't cross the date line
0433                 // Case 1: eastern boundary of other box intersects:
0434                 if (    (d->m_east >= other.d->m_east && d->m_west <= other.d->m_east)
0435                         // Case 2: eastern boundary of this box intersects:
0436                         || (other.d->m_east >= d->m_east && other.d->m_west <= d->m_east)
0437                         // Case 3: western boundary of other box intersects:
0438                         || (d->m_east >= other.d->m_west && d->m_west <= other.d->m_west)
0439                         // Case 4: western boundary of this box intersects:
0440                         || (other.d->m_east >= d->m_west && other.d->m_west <= d->m_west)) {
0441                     return true;
0442                 }
0443             }
0444             else {
0445                 // The other bounding box crosses the date line, "this" one does not:
0446                 // So the date line splits the other bounding box in two parts.
0447 
0448                 if ( d->m_west <= other.d->m_east || d->m_east >= other.d->m_west) {
0449                     return true;
0450                 }
0451             }
0452         }
0453         else {
0454             if ( other.crossesDateLine() ) {
0455                 // The trivial case: both bounding boxes cross the date line and intersect
0456                 return true;
0457             }
0458             else {
0459                 // "This" bounding box crosses the date line, the other one does not.
0460                 // So the date line splits "this" bounding box in two parts.
0461                 //
0462                 // This also covers the case where this bounding box covers the whole
0463                 // longitude range ( -180 <= lon <= + 180 ).
0464                 if ( other.d->m_west <= d->m_east || other.d->m_east >= d->m_west ) {
0465                     return true;
0466                 }
0467             }
0468         }
0469     }
0470 
0471     return false;
0472 }
0473 
0474 GeoDataLatLonBox GeoDataLatLonBox::united( const GeoDataLatLonBox& other ) const
0475 {
0476     if ( isEmpty() ) {
0477         return other;
0478     }
0479 
0480     if ( other.isEmpty() ) {
0481         return *this;
0482     }
0483 
0484     GeoDataLatLonBox result;
0485 
0486     // use the position of the centers of the boxes to determine the "smallest"
0487     // box (i.e. should the total box go through IDL or not). this
0488     // determination does not depend on one box or the other crossing IDL too
0489     GeoDataCoordinates c1 = center();
0490     GeoDataCoordinates c2 = other.center();
0491 
0492     // do latitude first, quite simple
0493     result.setNorth(qMax( d->m_north, other.north() ) );
0494     result.setSouth( qMin( d->m_south, other.south() ) );
0495 
0496     qreal w1 = d->m_west;
0497     qreal w2 = other.west();
0498     qreal e1 = d->m_east;
0499     qreal e2 = other.east();
0500 
0501     bool const idl1 = d->m_east < d->m_west;
0502     bool const idl2 = other.d->m_east < other.d->m_west;
0503 
0504     if ( idl1 ) {
0505         w1 += 2* M_PI;
0506         e1 += 2* M_PI;
0507     }
0508     if ( idl2 ) {
0509         w2 += 2* M_PI;
0510         e2 += 2* M_PI;
0511     }
0512 
0513     // in the usual case, we take the maximum of east bounds, and
0514     // the minimum of west bounds. The exceptions are:
0515     // - centers of boxes are more than 180 apart
0516     //    (so the smallest box should go around the IDL)
0517     //
0518     // - 1 but not 2 boxes are crossing IDL
0519     if ( fabs( c2.longitude()-c1.longitude() ) > M_PI
0520          || ( idl1 ^ idl2 ) ) {
0521         // exceptions, we go the unusual way:
0522         // min of east, max of west
0523         result.setEast( qMin( e1, e2 ) );
0524         result.setWest( qMax( w1, w2 ) );
0525     }
0526     else {
0527         // normal case, max of east, min of west
0528         result.setEast( qMax( e1, e2 ) );
0529         result.setWest( qMin( w1, w2 ) );
0530     }
0531     return result;
0532 }
0533 
0534 GeoDataLatLonBox GeoDataLatLonBox::toCircumscribedRectangle() const
0535 {
0536     QVector<GeoDataCoordinates> coordinates;
0537     coordinates.reserve(4);
0538 
0539     coordinates.append( GeoDataCoordinates( west(), north() ) );
0540     coordinates.append( GeoDataCoordinates( west(), south() ) );
0541     coordinates.append( GeoDataCoordinates( east() + ( crossesDateLine() ? 2 * M_PI : 0 ), north() ) );
0542     coordinates.append( GeoDataCoordinates( east() + ( crossesDateLine() ? 2 * M_PI : 0 ), south() ) );
0543 
0544     const qreal cosRotation = cos( rotation() );
0545     const qreal sinRotation = sin( rotation() );
0546 
0547     qreal centerLat = center().latitude();
0548     qreal centerLon = center().longitude();
0549     if ( GeoDataLatLonBox( 0, 0, center().longitude(), west() ).crossesDateLine() ) {
0550         if ( !centerLon ) centerLon += M_PI;
0551         else centerLon += 2 * M_PI;
0552     }
0553 
0554     GeoDataLatLonBox box;
0555 
0556     bool northSet = false;
0557     bool southSet = false;
0558     bool eastSet = false;
0559     bool westSet = false;
0560 
0561     for ( const GeoDataCoordinates& coord: coordinates ) {
0562 
0563         const qreal lon = coord.longitude();
0564         const qreal lat = coord.latitude();
0565 
0566         const qreal rotatedLon = ( lon - centerLon ) * cosRotation - ( lat - centerLat ) * sinRotation + centerLon;
0567         const qreal rotatedLat = ( lon - centerLon ) * sinRotation + ( lat - centerLat ) * cosRotation + centerLat;
0568 
0569         if ( !northSet || rotatedLat > box.north() ) {
0570             northSet = true;
0571             box.setNorth( rotatedLat );
0572         }
0573 
0574         if ( !southSet || rotatedLat < box.south() ) {
0575             southSet = true;
0576             box.setSouth( rotatedLat );
0577         }
0578 
0579         if ( !westSet || rotatedLon < box.west() ) {
0580             westSet = true;
0581             box.setWest( rotatedLon );
0582         }
0583 
0584         if ( !eastSet || rotatedLon > box.east() ) {
0585             eastSet = true;
0586             box.setEast( rotatedLon );
0587         }
0588     }
0589 
0590     box.setBoundaries(box.north(), box.south(), box.east(), box.west());
0591 
0592     return box;
0593 }
0594 
0595 GeoDataLatLonBox& GeoDataLatLonBox::operator=( const GeoDataLatLonBox &other )
0596 {
0597     GeoDataObject::operator=( other );
0598     
0599     *d = *other.d;
0600     return *this;
0601 }
0602 
0603 GeoDataLatLonBox GeoDataLatLonBox::operator|( const GeoDataLatLonBox& other ) const
0604 {
0605     return united( other );
0606 }
0607 
0608 GeoDataLatLonBox& GeoDataLatLonBox::operator|=( const GeoDataLatLonBox& other )
0609 {
0610     *this = united( other );
0611     return *this;
0612 }
0613 
0614 
0615 void GeoDataLatLonBox::pack( QDataStream& stream ) const
0616 {
0617     GeoDataObject::pack( stream );
0618 
0619     stream << d->m_north << d->m_south << d->m_east << d->m_west << d->m_rotation;
0620 }
0621 
0622 void GeoDataLatLonBox::unpack( QDataStream& stream )
0623 {
0624     GeoDataObject::unpack( stream );
0625 
0626     stream >> d->m_north >> d->m_south >> d->m_east >> d->m_west >> d->m_rotation;
0627 }
0628 
0629 GeoDataLatLonBox GeoDataLatLonBox::fromLineString(  const GeoDataLineString& lineString  )
0630 {
0631     // If the line string is empty return an empty boundingbox
0632     if ( lineString.isEmpty() ) {
0633         return GeoDataLatLonBox();
0634     }
0635 
0636     qreal lon, lat;
0637     lineString.first().geoCoordinates( lon, lat );
0638     GeoDataCoordinates::normalizeLonLat( lon, lat );
0639 
0640     qreal north = lat;
0641     qreal south = lat;
0642     qreal west =  lon;
0643     qreal east =  lon;
0644 
0645     // If there's only a single node stored then the boundingbox only contains that point
0646     if ( lineString.size() == 1 )
0647         return GeoDataLatLonBox( north, south, east, west );
0648 
0649     // Specifies whether the polygon crosses the IDL
0650     bool idlCrossed = false;
0651 
0652     // "idlCrossState" specifies the state concerning IDL crossage.
0653     // This is needed in order to create optimal bounding boxes in case of covering the IDL 
0654     // Every time the IDL gets crossed from east to west the idlCrossState value gets 
0655     // increased by one.
0656     // Every time the IDL gets crossed from west to east the idlCrossState value gets 
0657     // decreased by one.
0658     
0659     int idlCrossState = 0;
0660     int idlMaxCrossState = 0;
0661     int idlMinCrossState = 0;
0662 
0663     // Holds values for east and west while idlCrossState != 0
0664     qreal otherWest =  lon;
0665     qreal otherEast =  lon;
0666     
0667     qreal previousLon = lon;
0668 
0669     int currentSign = ( lon < 0 ) ? -1 : +1;
0670     int previousSign = currentSign;
0671 
0672     QVector<GeoDataCoordinates>::ConstIterator it( lineString.constBegin() );
0673     QVector<GeoDataCoordinates>::ConstIterator itEnd( lineString.constEnd() );
0674 
0675     bool processingLastNode = false;
0676 
0677     while( it != itEnd ) {
0678         // Get coordinates and normalize them to the desired range.
0679         (it)->geoCoordinates( lon, lat );
0680         GeoDataCoordinates::normalizeLonLat( lon, lat );
0681 
0682         // Determining the maximum and minimum latitude
0683         if ( lat > north ) {
0684             north = lat;
0685         } else if ( lat < south ) {
0686             south = lat;
0687         }
0688 
0689         currentSign = ( lon < 0 ) ? -1 : +1;
0690 
0691         // Once the polyline crosses the dateline the covered bounding box
0692         // would cover the whole [-M_PI; M_PI] range.
0693         // When looking separately at the longitude range that gets covered
0694         // east and west from the IDL we get two bounding boxes (we prefix
0695         // the resulting longitude range on the "other side" with "other").
0696         // By picking the "inner" range values we get a more appropriate 
0697         // optimized single bounding box.
0698 
0699         // IDL check
0700         if ( previousSign != currentSign
0701              && fabs( previousLon ) + fabs( lon ) > M_PI ) {
0702 
0703             // Initialize values for otherWest and otherEast
0704             if ( idlCrossed == false ) {
0705                 otherWest =  lon;
0706                 otherEast =  lon;
0707                 idlCrossed = true;
0708             }
0709 
0710             // Determine the new IDL Cross State
0711             if ( previousLon < 0 ) {
0712                 idlCrossState++;
0713                 if ( idlCrossState > idlMaxCrossState ) {
0714                     idlMaxCrossState = idlCrossState;
0715                 }
0716             }
0717             else {
0718                 idlCrossState--;
0719                 if ( idlCrossState < idlMinCrossState ) {
0720                     idlMinCrossState = idlCrossState;
0721                 }
0722             }
0723         }
0724 
0725         if ( idlCrossState == 0 ) {
0726             if ( lon > east ) east = lon;
0727             if ( lon < west ) west = lon;
0728         }
0729         else {
0730             if ( lon > otherEast ) otherEast = lon;
0731             if ( lon < otherWest ) otherWest = lon;
0732         }
0733 
0734         previousLon = lon;
0735         previousSign = currentSign;
0736 
0737         if ( processingLastNode ) {
0738             break;
0739         }
0740         ++it;
0741 
0742         if( lineString.isClosed() && it == itEnd ) {
0743                 it = lineString.constBegin();
0744                 processingLastNode = true;
0745         }
0746     }
0747 
0748     if ( idlCrossed ) {
0749         if ( idlMinCrossState < 0 ) {
0750             east = otherEast;
0751         }
0752         if ( idlMaxCrossState > 0 ) {
0753             west = otherWest;
0754         }
0755         if ( ( idlMinCrossState < 0 && idlMaxCrossState > 0 ) 
0756             || idlMinCrossState < -1  || idlMaxCrossState > 1 
0757             || west <= east ) {
0758             east = +M_PI;
0759             west = -M_PI;
0760             // if polygon fully in south hemisphere, contain south pole
0761             if( north < 0 ) {
0762                 south = -M_PI/2;
0763             } else {
0764                 north = M_PI/2;
0765             }
0766         }
0767     }
0768 
0769     return GeoDataLatLonBox( north, south, east, west );
0770 }
0771 
0772 bool GeoDataLatLonBox::isNull() const
0773 {
0774     return d->m_north == d->m_south && d->m_east == d->m_west;
0775 }
0776 
0777 bool GeoDataLatLonBox::isEmpty() const
0778 {
0779     return *this == empty;
0780 }
0781 
0782 bool GeoDataLatLonBox::fuzzyCompare(const GeoDataLatLonBox& lhs,
0783                                            const GeoDataLatLonBox& rhs,
0784                                            const qreal factor)
0785 {
0786     bool equal = true;
0787 
0788     // Check the latitude for approximate equality
0789 
0790     double latDelta = lhs.height() * factor;
0791 
0792     if (fabs(lhs.north() - rhs.north()) > latDelta) equal = false;
0793     if (fabs(lhs.south() - rhs.south()) > latDelta) equal = false;
0794 
0795 
0796     // Check the longitude for approximate equality
0797 
0798     double lonDelta = lhs.width() * factor;
0799 
0800     double lhsEast = lhs.east();
0801     double rhsEast = rhs.east();
0802 
0803     if (!GeoDataLatLonBox::crossesDateLine(lhsEast, rhsEast)) {
0804         if (fabs(lhsEast - rhsEast) > lonDelta) equal = false;
0805     }
0806     else {
0807         if (lhsEast < 0 && rhsEast > 0) {
0808             lhsEast += 2 * M_PI;
0809             if (fabs(lhsEast - rhsEast) > lonDelta) equal = false;
0810         }
0811         if (lhsEast > 0 && rhsEast < 0) {
0812             rhsEast += 2 * M_PI;
0813             if (fabs(lhsEast - rhsEast) > lonDelta) equal = false;
0814         }
0815     }
0816 
0817     double lhsWest = lhs.west();
0818     double rhsWest = rhs.west();
0819 
0820     if (!GeoDataLatLonBox::crossesDateLine(lhsWest, rhsWest)) {
0821        if (fabs(lhsWest - rhsWest) > lonDelta) equal = false;
0822     }
0823     else {
0824         if (lhsWest < 0 && rhsWest > 0) {
0825             lhsWest += 2 * M_PI;
0826             if (fabs(lhsWest - rhsWest) > lonDelta) equal = false;
0827         }
0828         if (lhsWest > 0 && rhsWest < 0) {
0829             rhsWest += 2 * M_PI;
0830             if (fabs(lhsWest - rhsWest) > lonDelta) equal = false;
0831         }
0832     }
0833 
0834     return equal;
0835 }
0836 
0837 
0838 void GeoDataLatLonBox::clear()
0839 {
0840     *this = empty;
0841 }
0842 }