-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
geospatial-tutorial.txt
235 lines (167 loc) · 7.55 KB
/
geospatial-tutorial.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
========================================
Find Restaurants with Geospatial Queries
========================================
.. default-domain:: mongodb
.. contents:: On this page
:local:
:backlinks: none
:depth: 1
:class: singlecol
Overview
--------
MongoDB's :term:`geospatial` indexing allows you to efficiently execute spatial
queries on a collection that contains geospatial shapes and points. This
tutorial will briefly introduce the concepts of geospatial indexes, and
then demonstrate their use with :query:`$geoWithin`, :query:`$geoIntersects`,
and :dbcommand:`geoNear`.
To showcase the capabilities of geospatial features and compare different
approaches, this tutorial will guide you through the process of writing queries
for a simple geospatial application.
Suppose you are designing a mobile application to help users find
restaurants in New York City. The application must:
- Determine the user's current neighborhood using :query:`$geoIntersects`,
- Show the number of restaurants in that neighborhood using
:query:`$geoWithin`, and
- Find restaurants within a specified distance of the user using
:query:`$nearSphere`.
This tutorial will use a ``2dsphere`` index to query for this data on spherical
geometry.
For more information on spherical and flat geometries, see
:ref:`geospatial-geometry`.
Distortion
----------
Spherical geometry will appear distorted when visualized on a map due to
the nature of projecting a three dimensional sphere, such as the earth,
onto a flat plane.
For example, take the specification of the spherical square defined by
the longitude latitude points ``(0,0)``, ``(80,0)``, ``(80,80)``, and
``(0,80)``. The following figure depicts the area covered by this region:
.. include:: /images/geospatial-spherical-square.rst
Searching for Restaurants
-------------------------
Prerequisites
~~~~~~~~~~~~~
Download the example datasets from
`<https://raw.githubusercontent.com/mongodb/docs-assets/geospatial/neighborhoods.json>`_ and
`<https://raw.githubusercontent.com/mongodb/docs-assets/geospatial/restaurants.json>`_.
These contain the collections ``restaurants`` and ``neighborhoods`` respectively.
After downloading the datasets, import them into the database:
.. code-block:: javascript
mongoimport <path to restaurants.json> -c restaurants
mongoimport <path to neighborhoods.json> -c neighborhoods
The :dbcommand:`geoNear` command requires a geospatial index, and almost
always improves performance of :query:`$geoWithin` and :query:`$geoIntersects`
queries.
Because this data is geographical, create a ``2dsphere`` index on each
collection using the :binary:`~bin.mongo` shell:
.. code-block:: javascript
db.restaurants.createIndex({ location: "2dsphere" })
db.neighborhoods.createIndex({ geometry: "2dsphere" })
Exploring the Data
~~~~~~~~~~~~~~~~~~
Inspect an entry in the newly-created ``restaurants`` collection from within the
:binary:`~bin.mongo` shell:
.. code-block:: javascript
db.restaurants.findOne()
This query returns a document like the following:
.. code-block:: javascript
{
location: {
type: "Point",
coordinates: [-73.856077, 40.848447]
},
name: "Morris Park Bake Shop"
}
This restaurant document corresponds to the location shown in the following
figure:
.. include:: /images/geospatial-single-point.rst
Because the tutorial uses a ``2dsphere`` index, the geometry data in the
``location`` field must follow the :doc:`GeoJSON format </reference/geojson>`.
Now inspect an entry in the ``neighborhoods`` collection:
.. code-block:: javascript
db.neighborhoods.findOne()
This query will return a document like the following:
.. code-block:: javascript
{
geometry: {
type: "Polygon",
coordinates: [[
[ -73.99, 40.75 ],
...
[ -73.98, 40.76 ],
[ -73.99, 40.75 ]
]]
},
name: "Hell's Kitchen"
}
This geometry corresponds to the region depicted in the following figure:
.. include:: /images/geospatial-polygon-hells-kitchen.rst
Find the Current Neighborhood
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Assuming the user’s mobile device can give a reasonably accurate location for
the user, it is simple to find the user's current neighborhood with
:query:`$geoIntersects`.
Suppose the user is located at -73.93414657 longitude and 40.82302903 latitude.
To find the current neighborhood, you will specify a point using the special
:query:`$geometry` field in :term:`GeoJSON` format:
.. code-block:: javascript
db.neighborhoods.findOne({ geometry: { $geoIntersects: { $geometry: { type: "Point", coordinates: [ -73.93414657, 40.82302903 ] } } } })
This query will return the following result:
.. code-block:: javascript
{
"_id" : ObjectId("55cb9c666c522cafdb053a68"),
"geometry" : {
"type" : "Polygon",
"coordinates" : [
[
[
-73.93383000695911,
40.81949109558767
],
...
]
]
},
"name" : "Central Harlem North-Polo Grounds"
}
Find all Restaurants in the Neighborhood
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can also query to find all restaurants contained in a given neighborhood.
Run the following in the :binary:`~bin.mongo` shell to find the neighborhood
containing the user, and then count the restaurants within that neighborhood:
.. code-block:: javascript
var neighborhood = db.neighborhoods.findOne( { geometry: { $geoIntersects: { $geometry: { type: "Point", coordinates: [ -73.93414657, 40.82302903 ] } } } } )
db.restaurants.find( { location: { $geoWithin: { $geometry: neighborhood.geometry } } } ).count()
This query will tell you that there are 127 restaurants in the requested
neighborhood, visualized in the following figure:
.. include:: /images/geospatial-all-restaurants.rst
Find Restaurants within a Distance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To find restaurants within a specified distance of a point, you can
use either :query:`$geoWithin` with :query:`$centerSphere` to return results
in unsorted order, or :query:`nearSphere` with :query:`$maxDistance` if you need
results sorted by distance.
Unsorted with ``$geoWithin``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To find restaurants within a circular region, use :query:`$geoWithin` with
:query:`$centerSphere`. :query:`$centerSphere` is a MongoDB-specific syntax to
denote a circular region by specifying the center and the radius in radians.
:query:`$geoWithin` does not return the documents in any specific order, so it
may show the user the furthest documents first.
The following will find all restaurants within five miles of the user:
.. code-block:: javascript
db.restaurants.find({ location:
{ $geoWithin:
{ $centerSphere: [ [ -73.93414657, 40.82302903 ], 5 / 3963.2 ] } } })
:query:`$centerSphere`'s second argument accepts the radius in radians, so you
must divide it by the radius of the earth in miles. See
:doc:`/tutorial/calculate-distances-using-spherical-geometry-with-2d-geospatial-indexes`
for more information on converting between distance units.
Sorted with ``$nearSphere``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
You may also use :query:`$nearSphere` and specify a :query:`$maxDistance` term
in meters. This will return all restaurants within five miles of the user in
sorted order from nearest to farthest:
.. code-block:: javascript
var METERS_PER_MILE = 1609.34
db.restaurants.find({ location: { $nearSphere: { $geometry: { type: "Point", coordinates: [ -73.93414657, 40.82302903 ] }, $maxDistance: 5 * METERS_PER_MILE } } })