File indexing completed on 2025-01-05 03:58:56
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 "digikam_debug.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 qCDebug(DIGIKAM_MARBLE_LOG) << Q_FUNC_INFO << "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 }