Reading here
http://docs.mongodb.org/manual/tutorial/query-a-2dsphere-index/
I find the following:
The following example queries grid coordinates and returns all documents
within a 10 mile radius of longitude 88 W and latitude 30 N. The example
converts the distance, 10 miles, to radians by dividing by the approximate
radius of the earth, 3959 miles:
db.places.find( { loc :
{ $geoWithin :
{ $centerSphere :
[ [ 88 , 30 ] , 10 / 3959 ]
} } } )
I think the "standard" notation is:
East is + (plus) and West is - (minus),
North is + (plus) and South is - (minus).
So why is West + (plus) in this example
on the MongoDB documentation site?
Is it really that way in MongoDB?
In fact, is there any standard which defines if West
maps to + or to - and the same for East, North, South?
See also:
Wikipedia - Latitude and longitude of the Earth
Wikipedia - Geographic coordinate system
Related
I am creating an app where a user can create an event and see other events created by other users only if there are in a 10Km Radius. I am storing all the data in firestore.
This is how the app works, from the user side all the events are fetched and only those events are displayed whose distance is less than 10km.
The problem is if there are 10,000 events in the database and the user is in the 10km radius of only 10 events then obviously it will count as 10,000 reads which is too expensive.
Any suggestions for this problem?
One solution that I have in mind is to store data according to the geographical area but how to implement it is another problem.
You won't be charge for 10 000 reads but only the documents retrieved by your query, ten in your example.
Here's a good video from Firebase explaining their billing system : https://www.youtube.com/watch?time_continue=224&v=6NegFl9p_sE
Also keep in mind that for queries other than document reads, such as a request for a list of collection IDs, you are billed for one document read.
I suggest computing min and max longitude corresponding to 10 km distance from user longitude. You do the same with latitude and use those limits in Firestore query. Doing so You will have less events/reads and you can compute the exact distance to suppress events between 10 and 14 km radius... if necessary.
To compute your limit you can use the following formula from https://www.movable-type.co.uk/scripts/latlong.html (Destination point given distance and bearing from start point) and there's an online javascript calculator you can study.
φ2 = asin( sin φ1 ⋅ cos δ + cos φ1 ⋅ sin δ ⋅ cos θ )
λ2 = λ1 + atan2( sin θ ⋅ sin δ ⋅ cos φ1, cos δ − sin φ1 ⋅ sin φ2 )
where φ is latitude, λ is longitude, θ is the bearing (clockwise from north), δ is the angular distance d/R; d being the distance traveled in meters, R the earth’s radius (6371e3).
With bearing 0° you obtain LatitudeMax
With bearing 90° you obtain longitudeEast
With bearing 180° you obtain LatitudeMin
With bearing 270° you obtain longitudewest
Since earth is a sphere with longitude between -180° and 180°
longitudeMin = Min(longitudeEast, longitudeWest)
longitudeMax = Max(longitudeEast, longitudeWest)
And The Firestore part is like :
CollectionReference col = Firestore.instance.collection("mycollection");
Query latitudeMinQuery = col.where('latitude', isGreaterThan: LatitudeMin);
Query latitudeMaxQuery = latitudeMinQuery.where('latitude', isLessThan: LatitudeMin);
Query longitudeMinQuery = latitudeMaxQuery.where('longitude', isGreaterThan: LongitudeMin);
Query longitudeMaxQuery = longitudeMinQuery.where('latitude', isLessThan: LongitudeMax);
https://stackoverflow.com/a/43804487/9139407 (answers by #alex-mamo)
Hopes it helps!
Consider the following case :
I have stored trips/trajectories in mongodb as LineString and index is 2dsphere.
According to the image provided, Trip 1 is a trajectory that a user wants to search for and Trip2-6 are trips that are already stored on mongodb.
Given a maxDistance on $near, Trip1 should be "matched" with Trip 3 and 4 as shown.
However $geointersects seem to accept a Polygon or Multipolygon as $geometry type and $near seem to accept only Point.
Is there any time efficient way to implement the following scenario with mongo queries?
Thanks!
EDIT : I changed geometry to Polygon, as Alex Blex said.
Visualisation of data (Trip 1 is the search Trip, Trip2-3 are stored in db)
So we have the following documents stored on mongo:
Trip2
tripData: Object
{
type: Polygon
coordinates: [
[ [8,2] , [7,3] , [7,4], [8,2] ]
]
}
Trip3
tripData: Object
{
type: Polygon
coordinates: [
[ [3,1], [4,1], [4,1.9999], [3,1] ]
]
}
Trip 1 is the trip we search for
tripData: Object
{
type: Polygon
coordinates: [
[ [2,2] , [1,4] , [3,5] , [4,2] , [2,2] ]
]
}
The query i run is the following :
db.trips.find({ tripData: { $geoIntersects : { $geometry : trip1 } } } )
Nothing is returned from this query as expected, because trips do not intersect as you can see in the Visualisation. How can i modify the query in order to match Trip1 with Trip3 using $near operator ?
geoIntersects requires polygons or multipolygons in the query, i.e. Trip1 in the question. Trip2-6 are LineString stored in the documents, which is perfectly fine. So the only extra thing to do is to convert Trip1 to polygon using offset, shown as lime near in the question.
Let's consider straight line first. The function to convert line [[x1,y1][x2,y2]] to polygon with offset d can be as simple as:
function LineToPolyWithFalsePositive(line, d) {
var teta = Math.atan2(line[1][0] - line[0][0], line[1][1] - line[0][1]);
var s = Math.sin(teta);
var c = Math.cos(teta);
return [
[line[0][0] - d*s - d*c, line[0][1] - d*c + d*s],
[line[1][0] + d*s - d*c, line[1][1] + d*c + d*s],
[line[1][0] + d*s + d*c, line[1][1] + d*c - d*s],
[line[0][0] - d*s + d*c, line[0][1] - d*c - d*s]
];
}
or
function LineToPolyWithFalseNegative(line, d) {
var teta = Math.atan2(line[1][0] - line[0][0], line[1][1] - line[0][1]);
var s = Math.sin(teta);
var c = Math.cos(teta);
return [
[line[0][0] - d*s, line[0][1] - d*c],
[line[0][0] - d*c, line[0][1] + d*s],
[line[1][0] - d*c, line[1][1] + d*s],
[line[1][0] + d*s, line[1][1] + d*c],
[line[1][0] + d*c, line[1][1] - d*s],
[line[0][0] + d*c, line[0][1] - d*s]
];
}
Which produce lime polygons as on the image below:
The returned value can be used in geoIntersects query against documents with LineString locations.
The problematic areas highlighted with red. The first poly covers distance more than d on edge cases, and the second poly covers less distance than d in the same edge cases.
If it was the only problem, I would go with false negative approach and run 2 more near queries for Points [x1,y1] and [x2,y2] to check if there are any missed documents in the highlighted areas.
If Trip1 is a complex LineString, there are much more calculations need to be done to convert it to polygon. See the image:
Apart from the edge cases for first and last point, there are similar problems for start and end of each segment. Basically you will need to calculate an angle between each segment to workout corresponded vertices of the polygon. Still doable thou. In the false-negative version of the polygon, the vertices circled with red should be cut, again considering the angle between segments.
If the Trip1 line in the query have many segments, it may be quite expensive, as you will need to run near query for each vertex + 2 for terminal points.
As a pragmatic approach, if it is acceptable, the false-positive version may work quite fast, as it is a single query.
Latitude: 22.744812,
Longitude: 75.892578
The above would be considered my center point.
And now I need to determine the latitude and longitude points from center point 1000 meter outward to each NSWE corners. So I would have a central long/lat, N, S, E and W long/lat..
So I would end up with 4 additional lat/long pairs.
What I am trying to resolve is a formula, preferably that can be done on a standard calculator to determine these 4 NSWE points based on the central point.
You could use MapKit for that:
- (CLLocationCoordinate2D *) calculateSquareCoordinates:(CLLocation*)center withRadius:(float)radius{
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(center.coordinate, radius*2, radius*2);
CLLocationCoordinate2D points[4];
points[0] = CLLocationCoordinate2DMake(region.center.latitude - region.span.latitudeDelta/2, region.center.longitude - region.span.longitudeDelta/2);
points[1] = CLLocationCoordinate2DMake(region.center.latitude + region.span.latitudeDelta/2, region.center.longitude - region.span.longitudeDelta/2);
points[2] = CLLocationCoordinate2DMake(region.center.latitude + region.span.latitudeDelta/2, region.center.longitude + region.span.longitudeDelta/2);
points[3] = CLLocationCoordinate2DMake(region.center.latitude - region.span.latitudeDelta/2, region.center.longitude + region.span.longitudeDelta/2);
return points;
}
and just call
CLLocationCoordinate2D *fourPoints = [self calculateSquareCoordinates:center withRadius:1000];
on your code.
you will have to use the Haversine formula to calculate the Lat/Long based on distance from a starting Lat/Long. have a look at this Link
The average radius of the earth is around 6371000 metres. This means that
1 degree of lattitude is equivalent to 6371000 * PI / 180 metres
(NB: PI = 3.14159... etc). However, 1 degree of longitude depends on the lattitude that you are. At the equator, one degree of longitude corresponds to the same distance in metres as 1 degree of lattitude. However, at the north and south poles, all longitude values are the same point (i.e. the pole itself), so 1 degree of longitude at the poles is zero metres. The formula for longitude is
1 degree of longitude is equivalent to 637100 * PI / 180 * COS(Lattitude)
where COS is the trigonometric cosine function. If you make these conversions, then you can do the calculation on a standard calculator. However, be aware that these are approximations that work well over short distances (e.g. less than a few hundred kilometers), but over long distances (e.g. thousands of kilometers) they become more and more inaccurate.
I need to convert kilometers to radians. Is this correct formula?
I need radians for nearsphere in MongoDB.
If I need to convert 5 kilometers to radians I do this:
5/6371
And I get this result (does it seem correct):
0.000784806153
UPDATE
This is not a math issue, I really need to know if I am doing the correct calculations from kilometers to radians to be able to do geospatial queries with MongoDB.
Kilometers to radians or distance and radian conversion
I arrived here, was confused, then I watched some Khan academy videos and it made more sense at that point and then I was able to actually look at equations from other sources to further educate myself.
Here's my train of thought.
I see a diagram about radians and I first think radius from a geolocation point which is wrong.
Instead, imagine the earth cut perfectly in half and just focus on one of the halves.
Now face that half toward you and look at the math diagram.
Think of the math diagram as showing from the center of the earth measuring the edge of the earth based on the arc length, after all the earth is curved so any measurement will be curved on the surface of the earth.
Radians are like degrees in a circle and the arc length is literally the distance between A and B in the math diagram.
To you, its a straight line on a bird's eye view, but really it's just a curve in 3d space along the edge of the earth.
Eureka! A lightbulb went on in my head.
distance = earth radius * radians
Thus with some very easy algebra...
radians = distance / earth radius
km
radians = distance in km / 6371
mi
radians = distance in mi / 3959
Sometimes thinking it through is fun.
Double-check this... https://www.translatorscafe.com/unit-converter/en/length/7-89/kilometer-Earth%E2%80%99s%20equatorial%20radius/
Now in regards to Mongo v3.2 specifically using mongoose in node.js
Despite my best efforts, mongo would not behave correctly as documented for a $geoNear query on a 2d index. never worked
let aggregate = [
{
$geoNear: {
near: { type: 'Point', coordinates: lonLatArray },
spherical: false,
distanceField: 'dist.calculated',
includeLocs: 'dist.location',
maxDistance: distanceInMeters / (6371 * 1000),
query: {
mode: 'nearme',
fcmToken: { $exists: true }
}
}
},
{ $skip: skip },
{ $limit: LIMIT }
];
However, when I changed to a 2dsphere index, it worked perfectly.
let aggregate = [
{
$geoNear: {
near: { type: 'Point', coordinates: lonLatArray },
spherical: true,
distanceField: 'dist.calculated',
includeLocs: 'dist.location',
maxDistance: distanceInMeters,
query: {
mode: 'nearme',
fcmToken: { $exists: true }
}
}
},
{ $skip: skip },
{ $limit: LIMIT }
];
But education never seems like a waste of time.
You are correct.
In spherical geometry you divide the distance with the radius of the sphere.
Mind that you should keep the units. So if you calculate the sphere radius in kilometers then you should use distance in kilometer. If you use miles then you should use Earth radius in miles (approx: 3,963.2).
The equatorial radius of the Earth is approximately 3,963.2 miles or 6,378.1 kilometers.
Note : 1 KM = 0.621371 Mile
Here is some simple formulas for calculation :
100 KM to Miles : (100 * 0.621371)
100 KM to radiant : 100 / 6378.1
100 Miles to radiant : 100 / 3963.2
So if you have data in Kilometers then you must use (100 / 6378.1) and for Miles data you can use (100 / 3963.2)
To convert:
distance to radians: divide the distance by the radius of the sphere (e.g. the Earth) in the same units as the distance measurement.
radians to distance: multiply the radian measure by the radius of the sphere (e.g. the Earth) in the units system that you want to convert the distance to.
for Example: if you want to convert 5 miles in radians, then we need to divide the distance(5 miles) by the radius of the sphere(also in miles), which is 3959. then 5/3959 is 0.0012629451...
Thanks
> center = [50, 50]
> radius = 10
> db.places.find({"loc" : {"$within" : {"$center" : [center, radius]}}})
Is it 10 km, miles, feet, or meters?
Lat/long?
I must be the same units than your data. If you store location in meters, your query will operate in meters. You have to choose what units you want to use.
I you need to use lat/long, the units for the position of your objects will be in decimal degrees and the units for the distances will be in radians. You need to use the spherical model to handle the fact that earth is round.
They're in degrees - so 1 ~= 69 miles.