diff --git a/src/lib/marble/geodata/data/GeoDataLatLonBox.cpp b/src/lib/marble/geodata/data/GeoDataLatLonBox.cpp index 12f05b917..ed36148d7 100644 --- a/src/lib/marble/geodata/data/GeoDataLatLonBox.cpp +++ b/src/lib/marble/geodata/data/GeoDataLatLonBox.cpp @@ -1,826 +1,886 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2007 Andrew Manson // Copyright 2008-2009 Torsten Rahn // #include "GeoDataLatLonBox.h" #include "MarbleDebug.h" #include "GeoDataCoordinates.h" #include "GeoDataLineString.h" #include "GeoDataTypes.h" #include namespace Marble { const GeoDataLatLonBox GeoDataLatLonBox::empty = GeoDataLatLonBox(); class GeoDataLatLonBoxPrivate { public: GeoDataLatLonBoxPrivate() : m_north( 0.0 ), m_south( 0.0 ), m_east( 0.0 ), m_west( 0.0 ), m_rotation( 0.0 ) { } const char* nodeType() const { return GeoDataTypes::GeoDataLatLonBoxType; } qreal m_north; qreal m_south; qreal m_east; qreal m_west; qreal m_rotation; // NOT implemented yet! }; bool operator==( GeoDataLatLonBox const& lhs, GeoDataLatLonBox const& rhs ) { return lhs.d->m_west == rhs.d->m_west && lhs.d->m_east == rhs.d->m_east && lhs.d->m_north == rhs.d->m_north && lhs.d->m_south == rhs.d->m_south && lhs.d->m_rotation == rhs.d->m_rotation; } bool operator!=( GeoDataLatLonBox const& lhs, GeoDataLatLonBox const& rhs ) { return !( lhs == rhs ); } GeoDataLatLonBox::GeoDataLatLonBox() : GeoDataObject(), d( new GeoDataLatLonBoxPrivate ) { } GeoDataLatLonBox::GeoDataLatLonBox( qreal north, qreal south, qreal east, qreal west, GeoDataCoordinates::Unit unit ) : GeoDataObject(), d( new GeoDataLatLonBoxPrivate ) { setBoundaries( north, south, east, west, unit ); } GeoDataLatLonBox::GeoDataLatLonBox( const GeoDataLatLonBox & other ) : GeoDataObject( other ), d( new GeoDataLatLonBoxPrivate( *other.d ) ) { } GeoDataLatLonBox::~GeoDataLatLonBox() { delete d; } const char* GeoDataLatLonBox::nodeType() const { return d->nodeType(); } qreal GeoDataLatLonBox::north( GeoDataCoordinates::Unit unit ) const { if ( unit == GeoDataCoordinates::Degree ) { return d->m_north * RAD2DEG; } return d->m_north; } void GeoDataLatLonBox::setNorth( const qreal north, GeoDataCoordinates::Unit unit ) { switch( unit ){ default: case GeoDataCoordinates::Radian: d->m_north = GeoDataCoordinates::normalizeLat( north ); break; case GeoDataCoordinates::Degree: d->m_north = GeoDataCoordinates::normalizeLat( north * DEG2RAD ); break; } } qreal GeoDataLatLonBox::south( GeoDataCoordinates::Unit unit ) const { if ( unit == GeoDataCoordinates::Degree ) { return d->m_south * RAD2DEG; } return d->m_south; } void GeoDataLatLonBox::setSouth( const qreal south, GeoDataCoordinates::Unit unit ) { switch( unit ){ default: case GeoDataCoordinates::Radian: d->m_south = GeoDataCoordinates::normalizeLat( south ); break; case GeoDataCoordinates::Degree: d->m_south = GeoDataCoordinates::normalizeLat( south * DEG2RAD ); break; } } qreal GeoDataLatLonBox::east( GeoDataCoordinates::Unit unit ) const { if ( unit == GeoDataCoordinates::Degree ) { return d->m_east * RAD2DEG; } return d->m_east; } void GeoDataLatLonBox::setEast( const qreal east, GeoDataCoordinates::Unit unit ) { switch( unit ){ default: case GeoDataCoordinates::Radian: d->m_east = GeoDataCoordinates::normalizeLon( east ); break; case GeoDataCoordinates::Degree: d->m_east = GeoDataCoordinates::normalizeLon( east * DEG2RAD ); break; } } qreal GeoDataLatLonBox::west( GeoDataCoordinates::Unit unit ) const { if ( unit == GeoDataCoordinates::Degree ) { return d->m_west * RAD2DEG; } return d->m_west; } void GeoDataLatLonBox::setWest( const qreal west, GeoDataCoordinates::Unit unit ) { switch( unit ){ default: case GeoDataCoordinates::Radian: d->m_west = GeoDataCoordinates::normalizeLon( west ); break; case GeoDataCoordinates::Degree: d->m_west = GeoDataCoordinates::normalizeLon( west * DEG2RAD ); break; } } void GeoDataLatLonBox::setRotation( const qreal rotation, GeoDataCoordinates::Unit unit ) { switch( unit ){ default: case GeoDataCoordinates::Radian: d->m_rotation = rotation; break; case GeoDataCoordinates::Degree: d->m_rotation = rotation * DEG2RAD; break; } } qreal GeoDataLatLonBox::rotation( GeoDataCoordinates::Unit unit ) const { if ( unit == GeoDataCoordinates::Degree ) { return d->m_rotation * RAD2DEG; } return d->m_rotation; } void GeoDataLatLonBox::boundaries( qreal &north, qreal &south, qreal &east, qreal &west, GeoDataCoordinates::Unit unit ) const { switch( unit ){ default: case GeoDataCoordinates::Radian: north = d->m_north; south = d->m_south; east = d->m_east; west = d->m_west; break; case GeoDataCoordinates::Degree: north = d->m_north * RAD2DEG; south = d->m_south * RAD2DEG; east = d->m_east * RAD2DEG; west = d->m_west * RAD2DEG; break; } } void GeoDataLatLonBox::setBoundaries( qreal north, qreal south, qreal east, qreal west, GeoDataCoordinates::Unit unit ) { switch( unit ){ default: case GeoDataCoordinates::Radian: d->m_north = GeoDataCoordinates::normalizeLat( north ); d->m_south = GeoDataCoordinates::normalizeLat( south ); d->m_east = GeoDataCoordinates::normalizeLon( east ); d->m_west = GeoDataCoordinates::normalizeLon( west ); break; case GeoDataCoordinates::Degree: d->m_north = GeoDataCoordinates::normalizeLat( north * DEG2RAD ); d->m_south = GeoDataCoordinates::normalizeLat( south * DEG2RAD ); d->m_east = GeoDataCoordinates::normalizeLon( east * DEG2RAD ); d->m_west = GeoDataCoordinates::normalizeLon( west * DEG2RAD ); break; } } void GeoDataLatLonBox::scale(qreal verticalFactor, qreal horizontalFactor) const { GeoDataCoordinates const middle = center(); qreal const deltaY = 0.5 * height() * verticalFactor; qreal const deltaX = 0.5 * width() * horizontalFactor; d->m_north = qMin((middle.latitude() + deltaY), static_cast(M_PI/2)); d->m_south = qMax((middle.latitude() - deltaY), static_cast(-M_PI/2)); if (deltaX > 180 * DEG2RAD) { d->m_east = M_PI; d->m_west = -M_PI; } else { d->m_east = GeoDataCoordinates::normalizeLon(middle.longitude() + deltaX); d->m_west = GeoDataCoordinates::normalizeLon(middle.longitude() - deltaX); } } GeoDataLatLonBox GeoDataLatLonBox::scaled(qreal verticalFactor, qreal horizontalFactor) const { GeoDataLatLonBox result = *this; result.scale(verticalFactor, horizontalFactor); return result; } qreal GeoDataLatLonBox::width( GeoDataCoordinates::Unit unit ) const { return GeoDataLatLonBox::width( d->m_east, d->m_west, unit ); } qreal GeoDataLatLonBox::width( qreal east, qreal west, GeoDataCoordinates::Unit unit ) { qreal width = fabs( (qreal)( GeoDataLatLonBox::crossesDateLine(east, west) ? 2 * M_PI - west + east : east - west ) ); // This also covers the case where this bounding box covers the whole // longitude range ( -180 <= lon <= + 180 ). if ( width > 2 * M_PI ) { width = 2 * M_PI; } if ( unit == GeoDataCoordinates::Degree ) { return width * RAD2DEG; } return width; } qreal GeoDataLatLonBox::height( GeoDataCoordinates::Unit unit ) const { return GeoDataLatLonBox::height(d->m_north, d->m_south, unit); } qreal GeoDataLatLonBox::height(qreal north, qreal south, GeoDataCoordinates::Unit unit) { qreal height = fabs( (qreal)( south - north ) ); if ( unit == GeoDataCoordinates::Degree ) { return height * RAD2DEG; } return height; } bool GeoDataLatLonBox::crossesDateLine() const { return GeoDataLatLonBox::crossesDateLine(d->m_east, d->m_west); } bool GeoDataLatLonBox::crossesDateLine(qreal east, qreal west) { if ( east < west || ( east == M_PI && west == -M_PI ) ) { return true; } return false; } GeoDataCoordinates GeoDataLatLonBox::center() const { if( isEmpty() ) return GeoDataCoordinates(); if( crossesDateLine() ) return GeoDataCoordinates( GeoDataCoordinates::normalizeLon( east() + 2 * M_PI - ( east() + 2 * M_PI - west() ) / 2 ) , north() - ( north() - south() ) / 2 ); else return GeoDataCoordinates( east() - ( east() - west() ) / 2, north() - ( north() - south() ) / 2 ); } bool GeoDataLatLonBox::containsPole( Pole pole ) const { switch ( pole ) { case NorthPole: return ( 2 * north() == +M_PI ); break; case SouthPole: return ( 2 * south() == -M_PI ); break; default: case AnyPole: return ( 2 * north() == +M_PI || 2 * south() == -M_PI ); break; } mDebug() << Q_FUNC_INFO << "Invalid pole"; return false; } bool GeoDataLatLonBox::contains(qreal lon, qreal lat) const { if ( lat < d->m_south || lat > d->m_north ) { return false; } // We need to take care of the normal case ... if ( ( ( lon < d->m_west || lon > d->m_east ) && ( d->m_west < d->m_east ) ) || // ... and the case where the bounding box crosses the date line: ( ( lon < d->m_west && lon > d->m_east ) && ( d->m_west > d->m_east ) ) ) return false; return true; } bool GeoDataLatLonBox::contains( const GeoDataCoordinates &point ) const { qreal lon, lat; point.geoCoordinates( lon, lat ); return contains(lon, lat); } bool GeoDataLatLonBox::contains( const GeoDataLatLonBox &other ) const { // check the contain criterion for the latitude first as this is trivial: if ( d->m_north >= other.north() && d->m_south <= other.south() ) { if ( !crossesDateLine() ) { if ( !other.crossesDateLine() ) { // "Normal" case: both bounding boxes don't cross the date line if ( d->m_west <= other.west() && d->m_east >= other.east() ) { return true; } } else { // The other bounding box crosses the date line, "this" one does not: // So the date line splits the other bounding box in two parts. // Hence "this" bounding box could be fully contained by one of them. // So for both cases we are able to ignore the "overhanging" portion // and thereby basically reduce the problem to the "normal" case: if ( ( other.west() <= d->m_west && d->m_east <= +M_PI ) || ( other.east() >= d->m_east && d->m_west >= -M_PI ) ) { return true; } } } else { if ( other.crossesDateLine() ) { // Other "Simple" case: both bounding boxes cross the date line if ( d->m_west <= other.west() && d->m_east >= other.east() ) { return true; } } else { // "This" bounding box crosses the date line, the other one does not. // So the date line splits "this" bounding box in two parts. // Hence the other bounding box could be fully contained by one of them. // So for both cases we are able to ignore the "overhanging" portion // and thereby basically reduce the problem to the "normal" case: if ( ( d->m_west <= other.west() && other.east() <= +M_PI ) || ( d->m_east >= other.east() && other.west() >= -M_PI ) ) { return true; } // if this bounding box covers the whole longitude range ( -180 <= lon <= + 180 ) // then of course the "inner" bounding box is "inside" if ( d->m_west == -M_PI && d->m_east == +M_PI ) { return true; } } } } return false; } bool GeoDataLatLonBox::intersects( const GeoDataLatLonBox &other ) const { if ( isEmpty() || other.isEmpty() ) { return false; } // check the intersection criterion for the latitude first: // Case 1: northern boundary of other box intersects: if ( (d->m_north >= other.d->m_north && d->m_south <= other.d->m_north) // Case 2: northern boundary of this box intersects: || (other.d->m_north >= d->m_north && other.d->m_south <= d->m_north) // Case 3: southern boundary of other box intersects: || (d->m_north >= other.d->m_south && d->m_south <= other.d->m_south) // Case 4: southern boundary of this box intersects: || (other.d->m_north >= d->m_south && other.d->m_south <= d->m_south)) { if ( !crossesDateLine() ) { if ( !other.crossesDateLine() ) { // "Normal" case: both bounding boxes don't cross the date line // Case 1: eastern boundary of other box intersects: if ( (d->m_east >= other.d->m_east && d->m_west <= other.d->m_east) // Case 2: eastern boundary of this box intersects: || (other.d->m_east >= d->m_east && other.d->m_west <= d->m_east) // Case 3: western boundary of other box intersects: || (d->m_east >= other.d->m_west && d->m_west <= other.d->m_west) // Case 4: western boundary of this box intersects: || (other.d->m_east >= d->m_west && other.d->m_west <= d->m_west)) { return true; } } else { // The other bounding box crosses the date line, "this" one does not: // So the date line splits the other bounding box in two parts. if ( d->m_west <= other.d->m_east || d->m_east >= other.d->m_west) { return true; } } } else { if ( other.crossesDateLine() ) { // The trivial case: both bounding boxes cross the date line and intersect return true; } else { // "This" bounding box crosses the date line, the other one does not. // So the date line splits "this" bounding box in two parts. // // This also covers the case where this bounding box covers the whole // longitude range ( -180 <= lon <= + 180 ). if ( other.d->m_west <= d->m_east || other.d->m_east >= d->m_west ) { return true; } } } } return false; } GeoDataLatLonBox GeoDataLatLonBox::united( const GeoDataLatLonBox& other ) const { if ( isEmpty() ) { return other; } if ( other.isEmpty() ) { return *this; } GeoDataLatLonBox result; // use the position of the centers of the boxes to determine the "smallest" // box (i.e. should the total box go through IDL or not). this // determination does not depend on one box or the other crossing IDL too GeoDataCoordinates c1 = center(); GeoDataCoordinates c2 = other.center(); // do latitude first, quite simple result.setNorth(qMax( d->m_north, other.north() ) ); result.setSouth( qMin( d->m_south, other.south() ) ); qreal w1 = d->m_west; qreal w2 = other.west(); qreal e1 = d->m_east; qreal e2 = other.east(); bool const idl1 = d->m_east < d->m_west; bool const idl2 = other.d->m_east < other.d->m_west; if ( idl1 ) { w1 += 2* M_PI; e1 += 2* M_PI; } if ( idl2 ) { w2 += 2* M_PI; e2 += 2* M_PI; } // in the usual case, we take the maximum of east bounds, and // the minimum of west bounds. The exceptions are: // - centers of boxes are more than 180 apart // (so the smallest box should go around the IDL) // // - 1 but not 2 boxes are crossing IDL if ( fabs( c2.longitude()-c1.longitude() ) > M_PI || ( idl1 ^ idl2 ) ) { // exceptions, we go the unusual way: // min of east, max of west result.setEast( qMin( e1, e2 ) ); result.setWest( qMax( w1, w2 ) ); } else { // normal case, max of east, min of west result.setEast( qMax( e1, e2 ) ); result.setWest( qMin( w1, w2 ) ); } return result; } GeoDataLatLonBox GeoDataLatLonBox::toCircumscribedRectangle() const { QVector coordinates; coordinates.reserve(4); coordinates.append( GeoDataCoordinates( west(), north() ) ); coordinates.append( GeoDataCoordinates( west(), south() ) ); coordinates.append( GeoDataCoordinates( east() + ( crossesDateLine() ? 2 * M_PI : 0 ), north() ) ); coordinates.append( GeoDataCoordinates( east() + ( crossesDateLine() ? 2 * M_PI : 0 ), south() ) ); const qreal cosRotation = cos( rotation() ); const qreal sinRotation = sin( rotation() ); qreal centerLat = center().latitude(); qreal centerLon = center().longitude(); if ( GeoDataLatLonBox( 0, 0, center().longitude(), west() ).crossesDateLine() ) { if ( !centerLon ) centerLon += M_PI; else centerLon += 2 * M_PI; } GeoDataLatLonBox box; bool northSet = false; bool southSet = false; bool eastSet = false; bool westSet = false; foreach ( const GeoDataCoordinates& coord, coordinates ) { const qreal lon = coord.longitude(); const qreal lat = coord.latitude(); const qreal rotatedLon = ( lon - centerLon ) * cosRotation - ( lat - centerLat ) * sinRotation + centerLon; const qreal rotatedLat = ( lon - centerLon ) * sinRotation + ( lat - centerLat ) * cosRotation + centerLat; if ( !northSet || rotatedLat > box.north() ) { northSet = true; box.setNorth( rotatedLat ); } if ( !southSet || rotatedLat < box.south() ) { southSet = true; box.setSouth( rotatedLat ); } if ( !westSet || rotatedLon < box.west() ) { westSet = true; box.setWest( rotatedLon ); } if ( !eastSet || rotatedLon > box.east() ) { eastSet = true; box.setEast( rotatedLon ); } } box.setBoundaries( GeoDataCoordinates::normalizeLat( box.north() ), GeoDataCoordinates::normalizeLat( box.south() ), GeoDataCoordinates::normalizeLon( box.east() ), GeoDataCoordinates::normalizeLon( box.west() ) ); return box; } QString GeoDataLatLonBox::toString( GeoDataCoordinates::Unit unit ) const { switch( unit ){ default: case GeoDataCoordinates::Radian: return QString( "North: %1; West: %2; South: %3; East: %4" ) .arg( d->m_north ).arg( d->m_west ).arg( d->m_south ).arg( d->m_east ); break; case GeoDataCoordinates::Degree: return QString( "North: %1; West: %2; South: %3; East: %4" ) .arg( d->m_north * RAD2DEG ).arg( d->m_west * RAD2DEG ).arg( d->m_south * RAD2DEG ).arg( d->m_east * RAD2DEG ); break; } return QString( "GeoDataLatLonBox::text(): Error in unit: %1\n" ) .arg( unit ); } GeoDataLatLonBox& GeoDataLatLonBox::operator=( const GeoDataLatLonBox &other ) { GeoDataObject::operator=( other ); *d = *other.d; return *this; } GeoDataLatLonBox GeoDataLatLonBox::operator|( const GeoDataLatLonBox& other ) const { return united( other ); } GeoDataLatLonBox& GeoDataLatLonBox::operator|=( const GeoDataLatLonBox& other ) { *this = united( other ); return *this; } void GeoDataLatLonBox::pack( QDataStream& stream ) const { GeoDataObject::pack( stream ); stream << d->m_north << d->m_south << d->m_east << d->m_west << d->m_rotation; } void GeoDataLatLonBox::unpack( QDataStream& stream ) { GeoDataObject::unpack( stream ); stream >> d->m_north >> d->m_south >> d->m_east >> d->m_west >> d->m_rotation; } GeoDataLatLonBox GeoDataLatLonBox::fromLineString( const GeoDataLineString& lineString ) { // If the line string is empty return an empty boundingbox if ( lineString.isEmpty() ) { return GeoDataLatLonBox(); } qreal lon, lat; lineString.first().geoCoordinates( lon, lat ); GeoDataCoordinates::normalizeLonLat( lon, lat ); qreal north = lat; qreal south = lat; qreal west = lon; qreal east = lon; // If there's only a single node stored then the boundingbox only contains that point if ( lineString.size() == 1 ) return GeoDataLatLonBox( north, south, east, west ); // Specifies whether the polygon crosses the IDL bool idlCrossed = false; // "idlCrossState" specifies the state concerning IDL crossage. // This is needed in order to create optimal bounding boxes in case of covering the IDL // Every time the IDL gets crossed from east to west the idlCrossState value gets // increased by one. // Every time the IDL gets crossed from west to east the idlCrossState value gets // decreased by one. int idlCrossState = 0; int idlMaxCrossState = 0; int idlMinCrossState = 0; // Holds values for east and west while idlCrossState != 0 qreal otherWest = lon; qreal otherEast = lon; qreal previousLon = lon; int currentSign = ( lon < 0 ) ? -1 : +1; int previousSign = currentSign; QVector::ConstIterator it( lineString.constBegin() ); QVector::ConstIterator itEnd( lineString.constEnd() ); bool processingLastNode = false; while( it != itEnd ) { // Get coordinates and normalize them to the desired range. (it)->geoCoordinates( lon, lat ); GeoDataCoordinates::normalizeLonLat( lon, lat ); // Determining the maximum and minimum latitude if ( lat > north ) { north = lat; } else if ( lat < south ) { south = lat; } currentSign = ( lon < 0 ) ? -1 : +1; // Once the polyline crosses the dateline the covered bounding box // would cover the whole [-M_PI; M_PI] range. // When looking separately at the longitude range that gets covered // east and west from the IDL we get two bounding boxes (we prefix // the resulting longitude range on the "other side" with "other"). // By picking the "inner" range values we get a more appropriate // optimized single bounding box. // IDL check if ( previousSign != currentSign && fabs( previousLon ) + fabs( lon ) > M_PI ) { // Initialize values for otherWest and otherEast if ( idlCrossed == false ) { otherWest = lon; otherEast = lon; idlCrossed = true; } // Determine the new IDL Cross State if ( previousLon < 0 ) { idlCrossState++; if ( idlCrossState > idlMaxCrossState ) { idlMaxCrossState = idlCrossState; } } else { idlCrossState--; if ( idlCrossState < idlMinCrossState ) { idlMinCrossState = idlCrossState; } } } if ( idlCrossState == 0 ) { if ( lon > east ) east = lon; if ( lon < west ) west = lon; } else { if ( lon > otherEast ) otherEast = lon; if ( lon < otherWest ) otherWest = lon; } previousLon = lon; previousSign = currentSign; if ( processingLastNode ) { break; } ++it; if( lineString.isClosed() && it == itEnd ) { it = lineString.constBegin(); processingLastNode = true; } } if ( idlCrossed ) { if ( idlMinCrossState < 0 ) { east = otherEast; } if ( idlMaxCrossState > 0 ) { west = otherWest; } if ( ( idlMinCrossState < 0 && idlMaxCrossState > 0 ) || idlMinCrossState < -1 || idlMaxCrossState > 1 || west <= east ) { east = +M_PI; west = -M_PI; // if polygon fully in south hemisphere, contain south pole if( north < 0 ) { south = -M_PI/2; } else { north = M_PI/2; } } } return GeoDataLatLonBox( north, south, east, west ); } bool GeoDataLatLonBox::isNull() const { if ( d->m_north == d->m_south && d->m_east == d->m_west ) return true; return false; } bool GeoDataLatLonBox::isEmpty() const { return *this == empty; } +bool GeoDataLatLonBox::fuzzyCompare(const GeoDataLatLonBox& lhs, + const GeoDataLatLonBox& rhs, + const qreal factor) +{ + bool equal = true; + + // Check the latitude for approximate equality + + double latDelta = lhs.height() * factor; + + if (fabs(lhs.north() - rhs.north()) > latDelta) equal = false; + if (fabs(lhs.south() - rhs.south()) > latDelta) equal = false; + + + // Check the longitude for approximate equality + + double lonDelta = lhs.width() * factor; + + double lhsEast = lhs.east(); + double rhsEast = rhs.east(); + + if (!GeoDataLatLonBox::crossesDateLine(lhsEast, rhsEast)) { + if (fabs(lhsEast - rhsEast) > lonDelta) equal = false; + } + else { + lhsEast = GeoDataCoordinates::normalizeLat( lhsEast ); + rhsEast = GeoDataCoordinates::normalizeLat( rhsEast ); + if (lhsEast < 0 && rhsEast > 0) { + lhsEast += 2 * M_PI; + if (fabs(lhsEast - rhsEast) > lonDelta) equal = false; + } + if (lhsEast > 0 && rhsEast < 0) { + rhsEast += 2 * M_PI; + if (fabs(lhsEast - rhsEast) > lonDelta) equal = false; + } + } + + double lhsWest = lhs.west(); + double rhsWest = rhs.west(); + + if (!GeoDataLatLonBox::crossesDateLine(lhsWest, rhsWest)) { + if (fabs(lhsWest - rhsWest) > lonDelta) equal = false; + } + else { + lhsWest = GeoDataCoordinates::normalizeLat( lhsWest ); + rhsWest = GeoDataCoordinates::normalizeLat( rhsWest ); + if (lhsWest < 0 && rhsWest > 0) { + lhsWest += 2 * M_PI; + if (fabs(lhsWest - rhsWest) > lonDelta) equal = false; + } + if (lhsWest > 0 && rhsWest < 0) { + rhsWest += 2 * M_PI; + if (fabs(lhsWest - rhsWest) > lonDelta) equal = false; + } + } + + return equal; +} + + void GeoDataLatLonBox::clear() { *this = empty; } } diff --git a/src/lib/marble/geodata/data/GeoDataLatLonBox.h b/src/lib/marble/geodata/data/GeoDataLatLonBox.h index ee439abdc..ac6b39220 100644 --- a/src/lib/marble/geodata/data/GeoDataLatLonBox.h +++ b/src/lib/marble/geodata/data/GeoDataLatLonBox.h @@ -1,239 +1,249 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2007 Andrew Manson // Copyright 2008-2009 Torsten Rahn // #ifndef MARBLE_GEODATALATLONBOX_H #define MARBLE_GEODATALATLONBOX_H #include "MarbleGlobal.h" #include "GeoDataObject.h" #include "GeoDataCoordinates.h" #include "geodata_export.h" namespace Marble { class GeoDataLatLonBoxPrivate; class GeoDataLineString; /** * @short A class that defines a 2D bounding box for geographic data. * * GeoDataLatLonBox is a 2D bounding box that describes a geographic area * in terms of latitude and longitude. * * The bounding box gets described by assigning the northern, southern, * eastern and western boundary. * So usually the value of the eastern boundary is bigger than the * value of the western boundary. * * This is also true if the GeoDataLatLonBox covers the whole longitude * range from 180 deg West to 180 deg East. Notably in this case * the bounding box crosses the date line. * * If the GeoDataLatLonBox does not cover the whole longitude range but still * crosses the date line then the eastern boundary has got a smaller value than * the western one. */ class GEODATA_EXPORT GeoDataLatLonBox : public GeoDataObject { friend bool GEODATA_EXPORT operator==( GeoDataLatLonBox const& lhs, GeoDataLatLonBox const& rhs ); friend bool GEODATA_EXPORT operator!=( GeoDataLatLonBox const& lhs, GeoDataLatLonBox const& rhs ); public: GeoDataLatLonBox(); GeoDataLatLonBox( qreal north, qreal south, qreal east, qreal west, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ); GeoDataLatLonBox( const GeoDataLatLonBox & ); virtual ~GeoDataLatLonBox(); GeoDataLatLonBox& operator=( const GeoDataLatLonBox& other ); /// Provides type information for downcasting a GeoData virtual const char* nodeType() const; /** * @brief Get the northern boundary of the bounding box. * @return the latitude of the northern boundary. */ qreal north( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const; void setNorth( const qreal north, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ); /** * @brief Get the southern boundary of the bounding box. * @return the latitude of the southern boundary. */ qreal south( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const; void setSouth( const qreal south, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ); /** * @brief Get the eastern boundary of the bounding box. * @return the longitude of the eastern boundary. */ qreal east( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const; void setEast( const qreal east, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ); /** * @brief Get the western boundary of the bounding box. * @return the longitude of the western boundary. */ qreal west( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const; void setWest( const qreal west, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ); /** * @brief Get the rotation of the bounding box. * @return the rotation of the bounding box. */ qreal rotation( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const; void setRotation( const qreal rotation, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ); void boundaries( qreal &north, qreal &south, qreal &east, qreal &west, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const; void setBoundaries( qreal north, qreal south, qreal east, qreal west, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ); /** * @brief Changes the differences between the boundaries and the center by the given factor, keeping the center unchanged * @param verticalFactor Vertical scale factor (affects north and south boundaries) * @param horizontalFactor Horizontal scale factor (affects west and east boundaries) */ void scale(qreal verticalFactor, qreal horizontalFactor) const; GeoDataLatLonBox scaled(qreal verticalFactor, qreal horizontalFactor) const; /** * @brief Get the width of the longitude interval * @return the angle covered by the longitude range. */ qreal width( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const; /** * @brief Get the width of the longitude interval. East and west parameters are in radians. * @return the angle covered by the longitude range in given unit. */ static qreal width( qreal east, qreal west, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ); /** * @brief Get the height of the latitude interval * @return the angle covered by the latitude range. */ qreal height( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const; /** * @brief Get the height of the latitude interval. North and south parameters are in radians. * @return the angle covered by the latitude range in given unit. */ static qreal height( qreal north, qreal south, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ); /** * @brief Detect whether the bounding box crosses the IDL. * @return @c true the bounding box crosses the +/-180 deg longitude. * @c false the bounding box doesn't cross the +/-180 deg longitude. */ bool crossesDateLine() const; /** * @brief Detect whether the bounding box crosses the IDL. * @param east radians east. * @param west radians west. * @return @c true the bounding box crosses the +/-180 deg longitude. * @c false the bounding box doesn't cross the +/-180 deg longitude. */ static bool crossesDateLine(qreal east, qreal west); /** * @brief returns the center of this box * @return a coordinate, face-center of the box */ virtual GeoDataCoordinates center() const; /** * @brief Detect whether the bounding box contains one of the poles. * @return @c true the bounding box contains one of the poles. * @c false the bounding box doesn't contain one of the poles. */ bool containsPole( Pole pole = AnyPole ) const; virtual bool contains( const GeoDataCoordinates & ) const; bool contains( const GeoDataLatLonBox & ) const; /** * @brief Detect whether the bounding box contains a point of given lon and lat. * @param lon longitude in radians. * @param lat latitude in radians. * @return true if the box contains given point, false otherwise */ bool contains(qreal lon, qreal lat) const; //Optimized version for overlay painting virtual bool intersects( const GeoDataLatLonBox & ) const; /** * @brief Returns the bounding LatLonBox of this box with the given one. */ GeoDataLatLonBox united( const GeoDataLatLonBox& other) const; /** * @return Returns the smallest bounding box that contains this LatLonBox rotated with its given angle. */ GeoDataLatLonBox toCircumscribedRectangle() const; /** * @brief Create the smallest bounding box from a line string. * @return the smallest bounding box that contains the linestring. */ static GeoDataLatLonBox fromLineString( const GeoDataLineString& lineString ); /** * @brief Creates a text string of the bounding box */ virtual QString toString( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const; /** * @brief Indicates whether the bounding box only contains a single 2D point ("singularity"). * @return Return value is true if the height and the width of the bounding box equal zero. */ virtual bool isNull() const; /** * @brief Indicates whether the bounding box is not initialised (and contains nothing). * @return Return value is true if bounding box is not initialised. */ virtual bool isEmpty() const; + /** + * @brief Indicates whether two bounding boxes are roughly equal. + * The factor specifies the margin threshold relative to the left handside + * bounding box within which both bounding boxes are considered equal. + * @return Return value is true if both bounding box are approximately equal. + */ + static bool fuzzyCompare(const GeoDataLatLonBox& lhs, + const GeoDataLatLonBox& rhs, + const qreal factor = 0.01); + /** * @brief Resets the bounding box to its uninitialised state (and thus contains nothing). */ virtual void clear(); GeoDataLatLonBox operator|( const GeoDataLatLonBox& other ) const; /** * @brief Unites this bounding box with the given one. * @return Returns a reference to self. */ GeoDataLatLonBox& operator |=( const GeoDataLatLonBox& other) ; /// Serialize the contents of the feature to @p stream. virtual void pack( QDataStream& stream ) const; /// Unserialize the contents of the feature from @p stream. virtual void unpack( QDataStream& stream ); private: GeoDataLatLonBoxPrivate * const d; static const GeoDataLatLonBox empty; }; } Q_DECLARE_METATYPE( Marble::GeoDataLatLonBox ) #endif