Git Product home page Git Product logo

Comments (7)

doodlesbykumbi avatar doodlesbykumbi commented on July 20, 2024 1

@idc77 the method Point#Distance returns an Angle. To get the distance in km you'll need to multiply by the radius of the earth.

There's also some additional changes needed from your code to construct a Point from lat-long, see below.

package main

import (
	"fmt"

	"github.com/golang/geo/s2"
)

const earthRadiusKm = 6371.01 // See https://github.com/golang/geo/blob/master/s2/s2_test.go#L46-L51

func main() {
	lat1 := 9.1829321
	lng1 := 48.7758459
	lat2 := 5.03131
	lng2 := 39.542448

	pg1 := s2.PointFromLatLng(s2.LatLngFromDegrees(lat1, lng1))
	pg2 := s2.PointFromLatLng(s2.LatLngFromDegrees(lat2, lng2))

       sdist := pg2.Distance(pg1) * earthRadiusKm
	fmt.Printf("s2 distance %f\n", sdist)
}

from geo.

idc77 avatar idc77 commented on July 20, 2024

Hi @doodlesbykumbi thank you very much for your reply.

Something is really not adding up.
I'm comparing the results of MongoDB's distance calculation and
"github.com/kellydunn/golang-geo"
and
this package.

package main

import (
	"fmt"

	"github.com/golang/geo/s2"
	geo2 "github.com/kellydunn/golang-geo"
)

const earthRadiusKm = 6371.01 // See https://github.com/golang/geo/blob/master/s2/s2_test.go#L46-L51

func main() {
	lat1 := 9.1829321
	lng1 := 48.7758459
	// lat2 := 5.03131
	// lng2 := 39.542448
	lat2 := 0.272091
	lng2 := 45.937435
	p1 := geo2.NewPoint(9.1829321, 48.7758459)
	p2 := geo2.NewPoint(5.03131, 39.542448)

	dist := p1.GreatCircleDistance(p2)
	fmt.Printf("great circle distance: %f\n", dist)

	pg1 := s2.PointFromLatLng(s2.LatLngFromDegrees(lat1, lng1))
	pg2 := s2.PointFromLatLng(s2.LatLngFromDegrees(lat2, lng2))
	sdist := pg1.Distance(pg2) * earthRadiusKm
	odist := pg2.Distance(pg1) * earthRadiusKm
	fmt.Printf("s2 distance: %f\n", sdist)
	fmt.Printf("s2 r-distance: %f\n", odist)
}
great circle distance: 1118.298549
s2 distance: 1039.471777
s2 r-distance: 1039.471777

however
a $near query in MongoDB resulsts in 741940.633007093 meters

			geoStage := bson.D{{
				"$geoNear", bson.D{
					{"near", profile.Location},
					{"distanceField", "distance"},
					{"minDistance", 1},
					{"maxDistance", radius},
					{"query", bson.D{
						{"subject_id", m.TargetID},
					}},
					{"spherical", true},
				},
			},
			}

This is just to get the distance of "my profile" to "target profile".
Maybe it's because of the "spherical" = true

https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/

spherical boolean Optional. Determines how MongoDB calculates the distance between two points:When true, MongoDB uses $nearSphere semantics and calculates distances using spherical geometry.When false, MongoDB uses $near semantics: spherical geometry for 2dsphere indexes and planar geometry for 2d indexes.Default: false.

spherical

boolean

Optional. Determines how MongoDB calculates the distance between two points:

When true, MongoDB uses [$nearSphere](https://www.mongodb.com/docs/manual/reference/operator/query/nearSphere/#mongodb-query-op.-nearSphere) semantics and calculates distances using spherical geometry.
When false, MongoDB uses [$near](https://www.mongodb.com/docs/manual/reference/operator/query/near/#mongodb-query-op.-near) semantics: spherical geometry for [2dsphere](https://www.mongodb.com/docs/manual/core/2dsphere/) indexes and planar geometry for [2d](https://www.mongodb.com/docs/manual/core/2d/) indexes.

Default: false.

from geo.

rsned avatar rsned commented on July 20, 2024

It's definitely something on the mongo side. If I use Google Maps and "Measure Distance", I get close to 1100 km for
p1 = 9.1829321, 48.7758459
p2 = 5.03131, 39.542448

https://www.google.com/maps/dir/5.03131,+39.542448/9.1829321,48.7758459/@7.8185624,42.7915689,7z/data=!4m7!4m6!1m3!2m2!1d39.542448!2d5.03131!1m0!3e2

And ~1036 for
p1 = 9.1829321, 48.7758459
p2 = 0.272091, 45.937435

https://www.google.com/maps/dir/9.1829321,+48.7758459/0.272091,45.937435/@4.7679831,47.1160662,7z/data=!4m7!4m6!1m3!2m2!1d48.7758459!2d9.1829321!1m0!3e2

Are you sure the points you are using in the mongo query are the same as in the s2 call?

from geo.

idc77 avatar idc77 commented on July 20, 2024

Sorry I posted the wrong source.
I will post a new source

package main

import (
	"fmt"

	"github.com/golang/geo/s2"
	geo2 "github.com/kellydunn/golang-geo"
)

const earthRadiusKm = 6371.01 // See https://github.com/golang/geo/blob/master/s2/s2_test.go#L46-L51

func main() {
	lat1 := 9.1829321
	lng1 := 48.7758459
	// lat2 := 5.03131
	// lng2 := 39.542448#
	lat2 := 15.39967
	lng2 := 45.749428
	p1 := geo2.NewPoint(lat1, lng1)
	p2 := geo2.NewPoint(lat2, lng2)

	dist := p1.GreatCircleDistance(p2)
	fmt.Printf("great circle distance: %f\n", dist)

	pg1 := s2.PointFromLatLng(s2.LatLngFromDegrees(lat1, lng1))
	pg2 := s2.PointFromLatLng(s2.LatLngFromDegrees(lat2, lng2))
	sdist := pg1.Distance(pg2) * earthRadiusKm
	odist := pg2.Distance(pg1) * earthRadiusKm
	fmt.Printf("s2 distance: %f\n", sdist)
	fmt.Printf("s2 r-distance: %f\n", odist)
}
great circle distance: 765.406106
s2 distance: 765.407307
s2 r-distance: 765.407307

The non-spherical result in mongodb is 577683.4757611528 aka about 577 km.

p1 is a real location
p2 is made up of randomly generated values

Using google maps I couldn't get the exact location of p1
coordinates should be reverse in mongodb

package models

// Location is a GeoJSON type.
type Location struct {
	Type        string    `json:"type" bson:"type"`
	Coordinates []float64 `json:"coordinates" bson:"coordinates"`
}

// NewPoint returns a GeoJSON Point with longitude and latitude.
func NewPoint(long, lat float64) Location {
	return Location{
		"Point",
		[]float64{long, lat},
	}
}

mongodb saves the points in reverse order long, lat.
Maybe that is the mystery, I need to go for a walk to clear my mind, will return later.

from geo.

idc77 avatar idc77 commented on July 20, 2024

Okay, that is indeed the case.

package main

import (
	"fmt"

	"github.com/golang/geo/s2"
	geo2 "github.com/kellydunn/golang-geo"
)

const earthRadiusKm = 6371.01 // See https://github.com/golang/geo/blob/master/s2/s2_test.go#L46-L51

func main() {
	lat1 := 9.1829321
	lng1 := 48.7758459
	lat2 := 15.39967
	lng2 := 45.749428
	// p1 := geo2.NewPoint(lat1, lng1)
	p1 := geo2.NewPoint(lng1, lat1)
	// p2 := geo2.NewPoint(lat2, lng2)
	p2 := geo2.NewPoint(lng2, lat2)

	dist := p1.GreatCircleDistance(p2)
	fmt.Printf("great circle distance: %f\n", dist)

	// pg1 := s2.PointFromLatLng(s2.LatLngFromDegrees(lat1, lng1))
	pg1 := s2.PointFromLatLng(s2.LatLngFromDegrees(lng1, lat1))
	// pg2 := s2.PointFromLatLng(s2.LatLngFromDegrees(lat2, lng2))
	pg2 := s2.PointFromLatLng(s2.LatLngFromDegrees(lng2, lat2))
	sdist := pg1.Distance(pg2) * earthRadiusKm
	// odist := pg2.Distance(pg1) * earthRadiusKm
	fmt.Printf("s2 distance: %f\n", sdist)
	// fmt.Printf("s2 r-distance: %f\n", odist)
}

I swapped longitude and latitude and the resulsts are drumroll

great circle distance: 577.040408
s2 distance: 577.041313

MongoDB value: 577683.4757611528

So MongoDB requires long, lat format, but does the calculation wrong internall by mistaking lat for long and the other way around.

That's if I didn't do anything wrong.
I can't believe no one hit this issue before.

Thank you all for you help :) I'll report it to MongoDB.
Well... I would if they didn't make it such a PITA to do.
I guess it will remain an issue until someone with a MongoDB Jira account that doesn't use 2FA stumbles upon this.

from geo.

idc77 avatar idc77 commented on July 20, 2024

It wasn't mongo. It was me. I'm the mongo ;P Self-knowledge is the 1st step to enlightenment
But there are still slightly different results.

great circle distance: 577.040408
s2 distance: 577.041313
mongodb distance: 577683.475761

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/golang/geo/s2"
	geo "github.com/kellydunn/golang-geo"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	mopts "go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/mongo/readpref"
	"go.mongodb.org/mongo-driver/x/bsonx"
)

const earthRadiusKm = 6371.01 // See https://github.com/golang/geo/blob/master/s2/s2_test.go#L46-L51

var (
	Database          = "testgeo"
	DefaultTimeout    = time.Second * 10
	ProfileCollection = "profile"
)

type (
	Profile struct {
		ID       primitive.ObjectID `bson:"_id,omitempty" json:"id"`
		Location Location           `bson:"location" json:"location"`
	}
	ProfileResult struct {
		ID       primitive.ObjectID `bson:"_id,omitempty" json:"id"`
		Location Location           `bson:"location" json:"location"`
		Distance float64            `bson:"distance" json:"distance"`
	}
	Location struct {
		Type        string    `json:"type" bson:"type"`
		Coordinates []float64 `json:"coordinates" bson:"coordinates"`
	}
)

func main() {
	// lat1 := 9.1829321
	// lng1 := 48.7758459
	// lat2 := 45.749428
	//lng2 := 15.39967
	// lat1 := 9.653194
	// lng1 := 48.709797
	// lat2 := 9.182313
	// lng2 := 48.783187
	lng1 := 9.1829321
	lat1 := 48.7758459

	lng2 := 15.39967
	lat2 := 45.749428

	p1 := geo.NewPoint(lat1, lng1)
	// p1 := geo2.NewPoint(lng1, lat1)
	p2 := geo.NewPoint(lat2, lng2)
	// p2 := geo2.NewPoint(lng2, lat2)

	dist := p1.GreatCircleDistance(p2)
	fmt.Printf("great circle distance: %f\n", dist)

	pg1 := s2.PointFromLatLng(s2.LatLngFromDegrees(lat1, lng1))
	// pg1 := s2.PointFromLatLng(s2.LatLngFromDegrees(lng1, lat1))
	pg2 := s2.PointFromLatLng(s2.LatLngFromDegrees(lat2, lng2))
	// pg2 := s2.PointFromLatLng(s2.LatLngFromDegrees(lng2, lat2))
	sdist := pg1.Distance(pg2) * earthRadiusKm
	// odist := pg2.Distance(pg1) * earthRadiusKm
	fmt.Printf("s2 distance: %f\n", sdist)
	// fmt.Printf("s2 r-distance: %f\n", odist)
	// fmt.Printf("mongo result: %f\n", 5165738.082454552)

	mp1 := new(Profile)
	mp1.Location = NewPoint(lng1, lat1)

	mp2 := new(Profile)
	mp2.Location = NewPoint(lng2, lat2)

	client, e := mongoInit()
	if e != nil {
		log.Fatal(e.Error())
	}
	defer func() {
		if e = client.Disconnect(context.Background()); e != nil {
			panic(e)
		}
	}()

	res1, e := CreateProfile(client, mp1)
	if e != nil {
		log.Fatal(e.Error())
	}

	res2, e := CreateProfile(client, mp2)
	if e != nil {
		log.Fatal(e.Error())
	}

	mp1.ID = res1.InsertedID.(primitive.ObjectID)
	mp2.ID = res2.InsertedID.(primitive.ObjectID)

	var radius int32
	radius = int32(6371) * 1000
	geoStage := bson.D{{
		"$geoNear", bson.D{
			{"near", mp1.Location},
			{"distanceField", "distance"},
			{"minDistance", 1},
			{"maxDistance", radius},
			{"spherical", true},
		},
	},
	}
	result, e := AggregateProfile(client, mongo.Pipeline{geoStage})
	if e != nil {
		return
	}
	fmt.Printf("mongodb distance: %f\n", result[0].Distance)

}

func mongoInit() (*mongo.Client, error) {
	ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
	defer cancel()
	client, e := mongo.Connect(ctx, mopts.Client().ApplyURI("mongodb://localhost:27017"))
	if e != nil {
		return nil, e
	}

	ctx, cancel = context.WithTimeout(context.Background(), DefaultTimeout)
	defer cancel()
	e = client.Ping(ctx, readpref.Primary())
	if e != nil {
		return nil, e
	}

	// drop database
	_ = client.Database(Database).Drop(context.Background())

	// create geoJSON 2dsphere index
	ctx, cancel = context.WithTimeout(context.Background(), DefaultTimeout)
	defer cancel()

	db := client.Database(Database)
	indexOpts := mopts.CreateIndexes().SetMaxTime(DefaultTimeout)

	// Index to location 2dsphere type.
	pointIndexModel := mongo.IndexModel{
		Options: mopts.Index(),
		Keys: bsonx.MDoc{
			"location": bsonx.String("2dsphere"),
		},
	}

	profileIndexes := db.Collection("profile").Indexes()

	// _, e = profileIndexes.CreateOne(ctx, pointIndexModel, indexOpts)
	_, e = profileIndexes.CreateOne(ctx, pointIndexModel, indexOpts)
	if e != nil {
		return nil, e
	}

	return client, nil
}

// NewPoint returns a GeoJSON Point with longitude and latitude.
func NewPoint(long, lat float64) Location {
	return Location{
		"Point",
		[]float64{long, lat},
	}
}

func CreateProfile(client *mongo.Client, m *Profile) (*mongo.InsertOneResult, error) {
	ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
	defer cancel()
	c := client.Database(Database).Collection(ProfileCollection)
	return c.InsertOne(ctx, m)
}

func AggregateProfile(client *mongo.Client, pipeline interface{}) ([]*ProfileResult, error) {
	ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
	defer cancel()
	c := client.Database(Database).Collection(ProfileCollection)
	cursor, e := c.Aggregate(ctx, pipeline)
	if e != nil {
		return nil, e
	}
	ctx, cancel = context.WithTimeout(context.Background(), DefaultTimeout)
	defer cancel()
	var m []*ProfileResult
	if e := cursor.All(ctx, &m); e != nil {
		return nil, e
	}
	return m, nil
}

But it's close enough and the users won't need very high accuracy in their results.
Would be interesting to know what postgis returns there.
Anyhow another day wasted spent on self-enlightenment ;P

from geo.

idc77 avatar idc77 commented on July 20, 2024

If anyone is still reading, the postgis 3.2.1 on postgresql 14 result ahaha

SELECT ST_Distance(
               ST_GeographyFromText('Point(9.1829321 48.7758459)'),
               ST_GeographyFromText('Point(15.39967 45.749428)'))
           AS geography_distance;
578123.95280377

4 approaches, 4 different results
Very entertaining
In reality it's probably somewhere around 490km. I took 2 real life points and ran them through the above program and the result was 52km. Using Google Maps and walking,the "not a straight line" point to point distance was 43km. Maybe Google Maps take the distance from the sea level into account or from the center of the earth. Maybe it uses vectors to calculate.
But those calculations are all 2d on a sphere, right?

Somewhat interesting

1 row retrieved starting from 1 in 39 ms (execution: 3 ms, fetching: 36 ms)

That's about 200ms faster than MongoDB (but then again I assume MongoDB was going through 100k datasets and comparing the distance to min and max distance).

Have a nice day. This was my last post in this issue.

from geo.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.