[SERVER-22001] geoNear spherical returning incorrect distance Created: 23/Dec/15  Updated: 02/Feb/16  Resolved: 02/Feb/16

Status: Closed
Project: Core Server
Component/s: Geo
Affects Version/s: 3.0.6
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Sean Assignee: David Storch
Resolution: Done Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Attachments: File ideone_8nhK37.cpp    
Operating System: ALL
Steps To Reproduce:

> db.createCollection("circleTest")
> db.circleTest.insert("loc":{"type":"Point", "coordinates":[-24,40]})
> db.circleTest.createIndex({"loc":"2dsphere"})
> db.runCommand({geoNear:"circleTest",near:[-25,40],spherical:true,distanceMultiplier:6378.1})

Sprint: Repl F (01/29/16), Query 10 (02/22/16)
Participants:

 Description   

Result:

{
	"dis" : 85.27473543648324,
	"obj" : {
		"_id" : ObjectId("567a6af8f2f0548bc9a9a4f2"),
		"loc" : {
			"type" : "Point",
			"coordinates" : [
				-24,
				40
			]
		}
	}
},

These are the distances using different formulas:
"n-vector" : 111.2,
"haversine" : 111.2,
"vincenty" : 110.77,

Problem:
https://github.com/mongodb/mongo/blob/master/src/mongo/db/geo/shapes.cpp
double spheredist_rad(const Point& p1, const Point& p2) {

Looking at https://en.wikipedia.org/wiki/N-vector the below is wrong
double cross_prod = (cosy1 * cosx1 * cosy2 * cosx2) + (cosy1 * sinx1 * cosy2 * sinx2) + (siny1 * siny2);

Shoulg be:
double cross_prod = (cosy1 * cosx1 * cosy2 * cosx2) + (cosx1 * siny1 * cosx2 * siny2) + (sinx1 * sinx2);

//Example https://ideone.com/8nhK37
    #include <iostream>
    #include <math.h>
    using namespace std;
     
    struct Point {
        Point();
        Point(double x, double y);
        std::string toString() const;
     
        double x;
        double y;
    };
    Point::Point() : x(0), y(0) {}
    Point::Point(double x, double y) : x(x), y(y) {}
     
    double spheredist_rad(const Point& p1, const Point& p2) {
        double sinx1(sin(p1.x)), cosx1(cos(p1.x));
        double siny1(sin(p1.y)), cosy1(cos(p1.y));
        double sinx2(sin(p2.x)), cosx2(cos(p2.x));
        double siny2(sin(p2.y)), cosy2(cos(p2.y));
     
        double cross_prod = (cosy1 * cosx1 * cosy2 * cosx2) + (cosy1 * sinx1 * cosy2 * sinx2) + (siny1 * siny2);
     
        double cross_prod2 = (cosy1 * cosx1 * cosy2 * cosx2) + (cosx1 * siny1 * cosx2 * siny2) + (sinx1 * sinx2);
     
    	cout << (acos(cross_prod) * 6378.1) << endl;
    	cout << (acos(cross_prod2) * 6378.1)<< endl;
    }
     
    int main() {
    	Point p1 = Point(-25 * M_PI / 180, 40 * M_PI / 180);
    	Point p2 = Point(-24 * M_PI / 180, 40 * M_PI / 180);
     
    	spheredist_rad(p1, p2);
     
    	return 0;
    }



 Comments   
Comment by David Storch [ 02/Feb/16 ]

Hi sean_rand,

Thanks for reporting this issue. After some investigation, I believe that this is working as designed. You are absolutely correct that the distance between (-25 lat, 40 lng) and (-24 lat, 40 lng) is about 111km. However, the system interprets this query as finding the distance between (40 lng, -25 lat) and (40 lng, -24 lat) which is about 85km. (As an aside, recent versions of MongoDB, including 3.0.x and 3.2.x versions, are using the S2 Geometry Library to compute the great circle distance. The custom implementation of the N-vector computation you are referring to is not being exercised by your example geoNear command.)

As stated in the documentation for storing geospatial data in MongoDB, the data storage format is GeoJSON. This is somewhat buried in the docs, but GeoJSON does specify that latitude-longitude data is represented as (lng, lat) rather than (lat, lng): http://geojson.org/geojson-spec.html#positions.

Please feel free to reach out if you have any further questions.

Best,
Dave

Comment by Sean [ 23/Dec/15 ]

Example code

Generated at Thu Feb 08 03:59:06 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.