Instructor: In this lecture, we're gonna learn
about geospatial queries in order to implement
a really cool feature, which is to provide
a search functionality for tours within a certain
distance of a specified point.
So let's say you live in a certain point
and wanted to know which tours start at a certain
distance from you, like 250 miles, because you don't want
to drive further than that in order to start your
tour experience.
So that would be an awesome feature,
and a really nice use case of geospatial queries.
And in order to implement something like this,
here in our tour router, we could create a nice route,
something like this.
So I'm going to put it here before this general routes
here.
So let's say router dot route, and I'm going to call
this one tours within, okay?
Then we also need to specify the distance.
And so therefore we create a distance parameter here.
Next, we also need to specify the center,
and that's basically the point where you live.
So let's specify it around center and then slash,
and then lat and longitude.
So basically into this variable here,
you want to pass in the coordinates of the place
where you are.
So let's say you live in Los Angeles
and wanted to find all the tours within a distance
of 300 miles.
So here you would say 300,
and then here you would put the coordinates
of basically where you live, all right?
Then, let's also provide the option of specifying
the unit.
So if this distance here is in kilometers or in miles.
So let's say, slash unit,
and the unit as the parameter.
Now this way of specifying a URL is something
that we never did before.
So basically saying here center and slash
and putting the longitude and latitude after that,
and then slash unit,
and then after that the queries parameter.
And of course we could also make it so that
user should specify all of these options
using a query string, but this way it looks way cleaner
and it's also kind of a standard way of specifying URL's
which contain a lot of objects.
So what I was saying that instead we could do it
of course like this.
Tours distance and then the user would have to provide
the distance like this here.
And the center like this, let's say minus 40,
and 45, and then the unit also sets to miles.
Okay, so this has been one way using query strings
and we have done it before.
And actually not like this, but like this.
Okay, but instead we are going to specify
it again like this.
Which in my opinion looks way nicer.
So minus 40, 45.
Okay, so think that this here really looks a lot
cleaner than this.
Okay, and so this is another thing that I wanted
to show you is that it's kind of a standard of
specifying URL's like this, all right?
Anyways, for this route of course,
we need a route handler, and that's gonna be at
tour controller, and let's call this handler function
get tours within.
And so let's now go ahead and implement this method
to this handler.
So tour controller and right down here.
And let's start by getting all parameters.
So let me just copy all of this here.
And just so we know what our data looks like.
All right?
So this, and then this has a comment.
All right, so let's use a simple restructuring
to get all our data at once from the parameters.
And actually we need to say const,
and distance, and unit.
So all of that comes from request dot params.
Okay, so on req.params, we have dot distance,
dot center, and dot unit, right?
Because these are the names of the three parameters
that we specified here.
And actually it's not center,
it's latlng here, okay, so that's the correct one,
and then finally the unit.
So again, we use restructuring to get all these
variables at once.
Next up, let's actually get all coordinates from this
latitude longitude variable here.
So as we see, we expect the data in the format like this,
and so that makes it really easy to copy this data
from Google Maps.
So let me just quickly show that to you.
It's important that you understand how this kind of stuff
also works in practice.
So let's use the Los Angeles example here.
Okay, and so let's say you live somewhere here.
Okay, and so you can very easily get the latitude
and the longitude like this, okay?
So put that here, just as an example again.
And so this is the format.
It looks like latitude and longitude.
And so let's now create one variable for each of them.
So one for latitude and one for longitude.
So lat longitude dot split because it's a string
and we want to split it by comma.
And so that will then create an array of two elements,
and now we can again use destructering in order to save
these to the two variables that we're interested in.
So latitude and longitude.
Okay, next up, I want to test if we actually have
the longitude and latitude variables to find.
Because if not, then it means the user didn't specify
them in the required format.
So let's say if there is no latitude,
or no longitude, then we want to create a new error.
So as always, new app error, and I'm not sure
if we have it actually still, right here,
and actually we don't.
So we commented out this part because we no longer
needed the app error after creating our handler
factory functions, but now, we actually need it again,
and so let's bring it back.
And the message here is gonna be please provide,
in the format, like this.
And error code 400 for that request.
Great, and now just to see if all of this is
working correctly,
let's just log out of this to the console.
The distance that longitude and unit.
And let's also send a generic message here,
just to really finish the request to respond cycle.
And for that of course is the same as always.
So status success for now, and the of course later on
we will send the data that the query returns.
So actually let's copy this here.
Because this kind of is already what we want.
So back in Postman, let's close all of this.
This one we no longer need.
All right, we also don't need to be logged in,
and we also don't want any updates.
So anyway, we also need to get this part
which I'm too lazy to write out.
And so let's test that now.
And that didn't work,
and I think this one we called tours within actually.
Did we?
So tours within.
So I'm not sure why I wrote distance here
let's just quickly fix that here in our examples.
And so of course it's tours within.
And that's still not working, so let's take a look
and route implementation here actually.
So here we have that, and yes of course,
this is not correct at all.
So this one here should not be inside dot route, obviously.
But instead, it should be in the get method.
And so now that looks a lot more like what
we have here.
So dot route and then dot get.
So one less try and this time it worked.
And let's take a look at the console,
and indeed, here we get distance that is specified
the latitude to longitude, and the unit.
Great, and so now it's time to actually write
the query itself.
Now a geospatial query actually works quite similar
to a regular query.
So we're still going to write tours is equal to tour.find,
and of course, awaiting the result of this promise.
And then the old game of marking everything
as async and then catch the async and wrap our function
in there.
So we have all tours here unused,
and so let's go ahead and edit to the response object
right away.
So data, and now remember how we called it data.
And then tours.
Okay, and all we need to do is to specify
or filter object here.
So remember that we want to basically
query for start location,
because the start location field is what holds
the geospatial point where each tour starts.
And so that's exactly what we're searching for.
So, start location, and now we need to specify
the value that we're searching for.
And for that, we will now use a geospatial operator
called geo within.
As always, we need to specify the subject,
and then here, where we would earlier use like some
math operator like greater than,
this time we use a geospatial operator like this one.
Geo within, and this operator does exactly what it says.
Basically it finds documents within a certain geometry.
And that geometry is what we need to define as a next step.
So we want to find documents,
but where do we actually want to find these documents?
Well we want to find them inside of a sphere that starts
at this point that we defined,
and which has a radius of the distance that we defined.
So again with our example in Los Angeles,
if you specify the distance of 250 miles,
then that means you want to find all the tour documents
within a sphere that has a radius of 250 miles.
Okay, make sense?
And so now we need to pass the information here
into the geo within operator, okay?
And we do that by defining a center sphere.
Okay, and again, I know that this looks quite confusing,
but that's why I'm explaining it here step by step.
And also in a second, we're going to take a look
at the documentation.
So the center sphere operator takes an array
of the coordinates and of the radius.
And let's actually format the code here to at least
make it look a bit easier, okay?
Well it kind of looks the same, but anyway,
that's how you find the coordinates here.
And for that, we need yet another array,
and then the longitude and the latitude.
And that's right.
You first need to always define the longitude
and then the latitude, which is a bit counterintuitive
because usually coordinate pairs are always specified
with the latitude first, and the longitude first.
I think I mentioned it before that in geo adjacent,
it for some reason works like this.
So that is the center of the sphere.
Now we need to specify it's radius.
Now here we actually do not pass in the distance,
but instead it expects a radius in a special unit
called radians.
So let me put radius variable here,
and then in a second we are going to define it.
So let's now actually define the radius.
So again, the radius is basically the distance
that we want to have as the radius,
but converted to a special unit called radians.
And in order to get the radians,
we need to divide our distance by the radius
of the earth.
So that sounds a bit crazy but really this is how it works.
Okay, so now we actually need to take into consideration
our units here, because of course the radius of the earth
is different in miles then in kilometers.
So let's now do a turnery of greater here
and say that if the unit is equal to miles,
well then the result here should be distance.
So basically our original radius divided by 3963.2.
Okay, so again, that is the radius of the Earth in miles.
Okay, and otherwise,
we will then assume that it's kilometer.
And so if it is kilometers, then it is the distance
divided by 6,378.1 kilometers.
All right, so again, this kind of crazy conversion here
is necessary because normally it would expect
the radius of our sphere to be in radians.
And radians we get by dividing the distance
by the radius of the Earth.
Great, so we're almost ready to test this now.
Let's just add the results property that we used to have.
So with the number of results basically,
and then another very important thing
is that we actually in order to be able to do just
basic queries, we need to first attribute an index
to the field where the geospatial data
that we're searching for is stored.
So in this case, we need to add an index to start location.
So let's do that here in tour model.
So down here, we need yet another index.
Tour schema.index.
Start location, but now we're actually not going
to set it to one or minus one,
because this time it is a different index
that we need.
So for geospatial data, this index needs to be a
2D sphere index if the data describes real points
on the Earth like sphere.
Or instead, we can also use a 2D index if we're using
just fictional points on a simple two dimensional plane.
Now in this case of course, we are talking about real
points on the Earth's surface,
so we're going to use a 2D sphere index here.
So a 2D sphere like this.
Okay, and so we're basically telling that this start
location here should be indexed to a 2D sphere.
So an Earthlike sphere where all our data are located.
Great, and with that, we should now actually be ready
to test out our new route.
Okay, let's just increase this radius here a little bit
by 400 miles, and so let's see what we get.
Well, we get this weird looking error,
and I'm not really sure what that is.
And so, let's take a look.
And probably it's something wrong here with the radius,
but actually this is a great time to test out our debugger.
So I showed that to you a long time ago,
but this is a good use case for actually taking a look
at the debugger again.
So I think we have a NPM script for that,
it's called debug.
So we need to know this process, and then say NPM run debug.
Okay?
So that should open up this nice window.
And let's close up all of these files here.
Okay, and so we're in our controllers.
Tour controller.
And so now, let's set a break point right here.
Okay, because at this point, we will have all our
variables defined, and so we can then take a look
at their values in order to see what's going on.
So of course we could of done that with a simple
console.log, but in some situations where you have
a lot of stuff going on,
and it's also nice to use the debugger.
So we need to send our request.
And now we're in the debugger at our break point.
And so at this point in time,
our code has really stopped.
And we can see here that actually all our variables
seem to be defined.
Let's take a look at that here in local.
So we have a distance, we have the latitude,
the longitude, and also the radius.
So let's keep going here.
So we jumped right to then next line because this if
here didn't enter the if block.
And so now it's running all of these functions.
But let's step out of them actually,
and now we actually get tours undefined.
So if we now continue this, we will probably get
that same error again, right?
And so indeed, we do.
So it must be something here in this line, let's say,
and indeed, I think I found the error.
So that's now how you write sphere.
So sphere is more like this, right?
So I hope and I believe that actually this was the error.
So let's actually close up the debugger here,
and let's relieve, and actually that should of
updated my code here.
Let's see, and it actually didn't.
I'm not sure why that is, but all right.
Let's run this here again.
Not this command, okay anyway,
let's just do NPM start.
Close all of these.
And now try it again.
Ah, and now we get some real results here, nice.
So it tells me that these tours here are in that
400 mile distance that is specified.
But how can we really know that it's true?
Well actually we can use compass for this.
So if we open up compass here, we have something really nice
which is this schema.
So let's do that here in the tours.
And so we can now come to schema here
and then analyze this schema.
So analyze the schema, and so now here we have a nice
summary for all of our fields.
For example you see that the difficulty
is easy in 50% of the documents,
medium in 30%, and difficult in 20%.
You also see these durations.
So in 20% it's five, and also nine is also more popular
than the other durations.
Now what I'm really interested in here is the locations.
Or actually the start locations, so that's here.
And now normally, you would see a map here.
But right now, there is no map because we have
a document right now which doesn't have a start location.
So in order for this to work properly,
we need to get rid of that document.
So it was one of these testing documents
that we created, but which we now actually no longer need.
So let me see if it shows up here.
Maybe it's in the end.
So yeah, it's this new test tour.
Let's get rid of that.
Back to our schema, let's analyze it again.
Okay, so here we have the star applications.
And here you already see the map, so that's great, right?
That's really really a handy feature.
So we should have nine points here.
So one, two, three, four, five, six, seven, eight,
one up here, the ninth one.
So probably this one is to see the Northern Lights.
Right?
And now here we can actually replicate that query
using this graphical interface.
So let's come to Los Angeles and approximately
to the point where we were.
So let's say here, and I'm zooming out a little bit
just to give some more space,
but now we can hit shift and then drag.
So just as it says down here.
So put the hand where our point was,
so basically somewhere here in Los Angeles.
Now shift, and then drag, now all right.
And so you can see that as we start increasing
this sphere, we get more and more towards
some more of these points here turning yellow, okay?
And I believed that our radius was something like 0.1,
or something like this, let's say.
And so you see that actually three documents,
so three tours all within this sphere
that we just created, okay?
So we have this sphere with the radius of 0.1,
as you can see up here.
And so in fact this query looks just like the one
that we just filled with code in our controller function,
right?
So we get these three documents that are marked
as orange, so let's actually now hit analyze here again,
and so now our query returned three documents.
So let's see the names actually.
So where is that?
For the sports lover, the wine taster,
and the park camper.
And the three tours that we got in Postman,
so as a result for API, should be these exact same three.
So the sports lover, wine taster, and park camper.
So park camper, then you have,
the sports lover,
and the last one remember should be the wine taster,
and indeed it is.
Great, that's really fantastic.
Let's now just decrease this here to 200,
let's see how many results you get then,
and in fact it's only just one.
So let's come back to compass here.
Let's delete this query here.
So if we get back to all our documents,
take a look at our map.
And so here is LA, okay.
It's another radius should be something like 0.05.
Now something like this, let's say,
and so indeed we only get this one tour which I believe
is the wine taster or something.
Yeah, we have to analyze this.
So oh, actually it's the sports lover.
And so let's see if that's what we got here.
And indeed, it is.
So it's the sports lover, and so that is the only tour
that is within 200 miles of Los Angeles.
Fantastic.
So this I find really really exciting, really cool.
Let me just show you another thing very quickly,
so you can also see all of our locations on the map.
So that's here.
And so these are the locations of all of the tours,
so the ones that are stored in location.
So we have the coordinates, and we also have days,
descriptions, and the time, okay?
And of course our query is not about all of this here.
For that query we really just care about the
star locations, but I still wanted to show you this map
where we can actually see all of the locations as well.
Okay, so there really is a huge potential for using
geospatial data with MongoDB.
The possibilities are really endless for doing
geospatial queries like this.
And I actually wanted to show you if we have some more
geospatial operators in MongoDB.
So let's take a look at the documentation here again.
So let's come down here again to the reference
and operators, and query operators.
And I think it's somewhere down here, yeah.
So here we have the geospatial operator.
And you see that this is the one that we used,
so the one that selects geometries within a bounding
geo adjacent geometry.
That sounds a bit weird, but this bounding
geo adjacent geometry is that sphere that we defined
in our code, right?
Now we could also have used the snear here,
let's take a look at that.
And so that would of looked a bit different.
So in this case, we would of defined the point
using the longitude and the latitude that we got,
and then we would of specified the maximum distance
here in meters.
And then in this case, we didn't need to do the conversion
to radians.
Okay, what's also nice about this one
is that we can specify a minimum distance.
And so with this, we could for example exclude
tours that are only 50 miles away from our starting point,
okay?
So if you someday need something like this,
then you can always use the new operator here as well.
Okay, great.
So with this geospatial query that we just defined here,
we basically found documents that are located
within a certain distance of our starting point.
But what if we actually wanted to know the exact
distances of all the tours to that starting point?
Well that's exactly what we're going to calculate
in the next lecture.