From aa13edaae8449d76d16d3931428c6bd922cbb81f Mon Sep 17 00:00:00 2001 From: gregs Date: Fri, 6 May 2011 10:22:08 -0400 Subject: [PATCH] check spherical earth query bounds SERVER-2980 --- db/geo/2d.cpp | 31 +++++++++++++++++++---------- db/geo/core.h | 6 ++++++ jstests/geo_oob_sphere.js | 42 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 jstests/geo_oob_sphere.js diff --git a/db/geo/2d.cpp b/db/geo/2d.cpp index 2404bf6df8a..a2c864aff5e 100644 --- a/db/geo/2d.cpp +++ b/db/geo/2d.cpp @@ -1599,12 +1599,14 @@ namespace mongo { double approxDistance( const GeoHash& h ) { double approxDistance = -1; + Point p( _g, h ); switch (_type) { case GEO_PLAIN: - approxDistance = _near.distance( Point( _g, h ) ); + approxDistance = _near.distance( p ); break; case GEO_SPHERE: - approxDistance = spheredist_deg( _near, Point( _g, h ) ); + checkEarthBounds( p ); + approxDistance = spheredist_deg( _near, p ); break; default: assert( false ); } @@ -1636,14 +1638,17 @@ namespace mongo { double exactDistance = -1; bool exactWithin = false; + Point p( loc ); + // Get the appropriate distance for the type switch ( _type ) { case GEO_PLAIN: - exactDistance = _near.distance( Point( loc ) ); - exactWithin = _near.distanceWithin( Point( loc ), _maxDistance ); + exactDistance = _near.distance( p ); + exactWithin = _near.distanceWithin( p, _maxDistance ); break; case GEO_SPHERE: - exactDistance = spheredist_deg( _near, Point( loc ) ); + checkEarthBounds( p ); + exactDistance = spheredist_deg( _near, p ); exactWithin = ( exactDistance <= _maxDistance ); break; default: assert( false ); @@ -1723,6 +1728,7 @@ namespace mongo { _scanDistance = maxDistance + _spec->_error; } else if (type == GEO_SPHERE) { + checkEarthBounds( startPt ); // TODO: consider splitting into x and y scan distances _scanDistance = computeXScanDistance( startPt._y, rad2deg( _maxDistance ) + _spec->_error ); } @@ -1927,6 +1933,7 @@ namespace mongo { // Same, but compute maxDistance using spherical transform uassert(13461, "Spherical MaxDistance > PI. Are you sure you are using radians?", _maxDistance < M_PI); + checkEarthBounds( _startPt ); _type = GEO_SPHERE; _yScanDistance = rad2deg( _maxDistance ) + _g->_error; @@ -1973,10 +1980,13 @@ namespace mongo { d = _g->distance( _start , h ); error = _g->_error; break; - case GEO_SPHERE: - d = spheredist_deg( _startPt, Point( _g, h ) ); + case GEO_SPHERE: { + Point p( _g, h ); + checkEarthBounds( p ); + d = spheredist_deg( _startPt, p ); error = _g->_errorSphere; break; + } default: assert( false ); } @@ -1991,17 +2001,19 @@ namespace mongo { GEODEBUG( "Inexact distance : " << d << " vs " << _maxDistance << " from " << ( *i ).toString() << " due to error " << error ); + Point p( *i ); // Exact distance checks. switch (_type) { case GEO_PLAIN: { - if( _startPt.distanceWithin( Point( *i ), _maxDistance ) ) return true; + if( _startPt.distanceWithin( p, _maxDistance ) ) return true; break; } case GEO_SPHERE: // Ignore all locations not hashed to the key's hash, since spherical calcs are // more expensive. if( _g->_hash( *i ) != h ) break; - if( spheredist_deg( _startPt , Point( *i ) ) <= _maxDistance ) return true; + checkEarthBounds( p ); + if( spheredist_deg( _startPt , p ) <= _maxDistance ) return true; break; default: assert( false ); } @@ -2373,7 +2385,6 @@ namespace mongo { if ( cmdObj["spherical"].trueValue() ) type = GEO_SPHERE; - // We're returning exact distances, so don't evaluate lazily. GeoSearch gs( g , n , numWanted , filter , maxDistance , type ); if ( cmdObj["start"].type() == String) { diff --git a/db/geo/core.h b/db/geo/core.h index 684b6e49b4a..109495066c0 100644 --- a/db/geo/core.h +++ b/db/geo/core.h @@ -454,6 +454,12 @@ namespace mongo { extern const double EARTH_RADIUS_KM; extern const double EARTH_RADIUS_MILES; + // Technically lat/long bounds, not really tied to earth radius. + inline void checkEarthBounds( Point p ) { + uassert( 14808, str::stream() << "point " << p.toString() << " must be in earth-like bounds of long : [-180, 180), lat : [-90, 90] ", + p._x >= -180 && p._x < 180 && p._y >= -90 && p._y <= 90 ); + } + inline double deg2rad(double deg) { return deg * (M_PI/180); } inline double rad2deg(double rad) { return rad * (180/M_PI); } diff --git a/jstests/geo_oob_sphere.js b/jstests/geo_oob_sphere.js new file mode 100644 index 00000000000..d493f3698ba --- /dev/null +++ b/jstests/geo_oob_sphere.js @@ -0,0 +1,42 @@ +// +// Ensures spherical queries report invalid latitude values in points and center positions +// + +t = db.geooobsphere +t.drop(); + +t.insert({ loc : { x : 30, y : 89 } }) +t.insert({ loc : { x : 30, y : 89 } }) +t.insert({ loc : { x : 30, y : 89 } }) +t.insert({ loc : { x : 30, y : 89 } }) +t.insert({ loc : { x : 30, y : 89 } }) +t.insert({ loc : { x : 30, y : 89 } }) +t.insert({ loc : { x : 30, y : 91 } }) + +t.ensureIndex({ loc : "2d" }) +assert.isnull( db.getLastError() ) + +t.find({ loc : { $nearSphere : [ 30, 91 ], $maxDistance : 0.25 } }).count() +var err = db.getLastError() +assert( err != null ) +printjson( err ) + +t.find({ loc : { $nearSphere : [ 30, 89 ], $maxDistance : 0.25 } }).count() +var err = db.getLastError() +assert( err != null ) +printjson( err ) + +t.find({ loc : { $within : { $centerSphere : [[ -180, -91 ], 0.25] } } }).count() +var err = db.getLastError() +assert( err != null ) +printjson( err ) + +db.runCommand({ geoNear : "geooobsphere", near : [179, -91], maxDistance : 0.25, spherical : true }) +var err = db.getLastError() +assert( err != null ) +printjson( err ) + +db.runCommand({ geoNear : "geooobsphere", near : [30, 89], maxDistance : 0.25, spherical : true }) +var err = db.getLastError() +assert( err != null ) +printjson( err ) \ No newline at end of file