Skip to content

Commit fa0c1c6

Browse files
committed
Add direct geospatial index support
Since mongodb#371 was raised back in 2014 I’ve wanted a way to improve and simplify adding geospatial indexes.
1 parent 3276bac commit fa0c1c6

File tree

5 files changed

+280
-2
lines changed

5 files changed

+280
-2
lines changed

README.md

+90-2
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,32 @@ Supported operations are:
234234
- hasCollection
235235
- index and dropIndex (compound indexes supported as well)
236236
- unique
237-
- background, sparse, expire (MongoDB specific)
237+
- background, sparse, expire, geospatial (MongoDB specific)
238238

239239
All other (unsupported) operations are implemented as dummy pass-through methods, because MongoDB does not use a predefined schema. Read more about the schema builder on http://laravel.com/docs/schema
240240

241+
### Geospatial indexes
242+
243+
Geospatial indexes are handy for querying location-based documents. They come in two forms: `2d` and `2dsphere`. Use the schema builder to add these to a collection.
244+
245+
To add a `2d` index:
246+
247+
```php
248+
Schema::create('users', function($collection)
249+
{
250+
$collection->geospatial('name', '2d');
251+
});
252+
```
253+
254+
To add a `2dsphere` index:
255+
256+
```php
257+
Schema::create('users', function($collection)
258+
{
259+
$collection->geospatial('name', '2dsphere');
260+
});
261+
```
262+
241263
Extensions
242264
----------
243265

@@ -274,7 +296,7 @@ If you want to use MongoDB to handle failed jobs, change the database in `config
274296
],
275297
```
276298

277-
And add the service provider in `config/app.php`:
299+
And add the service provider in `config/app.php`:
278300

279301
```php
280302
Jenssegers\Mongodb\MongodbQueueServiceProvider::class,
@@ -525,6 +547,72 @@ Performs a modulo operation on the value of a field and selects documents with a
525547
User::where('age', 'mod', [10, 0])->get();
526548
```
527549

550+
**Near**
551+
552+
**NOTE:** Specify coordinates in this order: `longitude, latitude`.
553+
554+
```php
555+
$users = User::where('location', 'near', [
556+
'$geometry' => [
557+
'type' => 'Point',
558+
'coordinates' => [
559+
-0.1367563,
560+
51.5100913,
561+
],
562+
],
563+
'$maxDistance' => 50,
564+
]);
565+
```
566+
567+
**GeoWithin**
568+
569+
```php
570+
$users = User::where('location', 'geoWithin', [
571+
'$geometry' => [
572+
'type' => 'Polygon',
573+
'coordinates' => [[
574+
[
575+
-0.1450383,
576+
51.5069158,
577+
],
578+
[
579+
-0.1367563,
580+
51.5100913,
581+
],
582+
[
583+
-0.1270247,
584+
51.5013233,
585+
],
586+
[
587+
-0.1450383,
588+
51.5069158,
589+
],
590+
]],
591+
],
592+
]);
593+
```
594+
595+
**GeoIntersects**
596+
597+
```php
598+
$locations = Location::where('location', 'geoIntersects', [
599+
'$geometry' => [
600+
'type' => 'LineString',
601+
'coordinates' => [
602+
[
603+
-0.144044,
604+
51.515215,
605+
],
606+
[
607+
-0.129545,
608+
51.507864,
609+
],
610+
],
611+
],
612+
]);
613+
```
614+
615+
528616
**Where**
529617

530618
Matches documents that satisfy a JavaScript expression. For more information check http://docs.mongodb.org/manual/reference/operator/query/where/#op._S_where

src/Jenssegers/Mongodb/Schema/Blueprint.php

+25
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,31 @@ public function sparse($columns = null, $options = [])
144144
return $this;
145145
}
146146

147+
/**
148+
* Specify a geospatial index for the collection.
149+
*
150+
* @param string|array $columns
151+
* @param string $index
152+
* @param array $options
153+
* @return Blueprint
154+
*/
155+
public function geospatial($columns = null, $index = '2d', $options = [])
156+
{
157+
if ($index == '2d' or $index == '2dsphere') {
158+
$columns = $this->fluent($columns);
159+
160+
$columns = array_flip($columns);
161+
162+
foreach ($columns as $column => $value) {
163+
$columns[$column] = $index;
164+
}
165+
166+
$this->index($columns, null, null, $options);
167+
}
168+
169+
return $this;
170+
}
171+
147172
/**
148173
* Specify the number of seconds after wich a document should be considered expired based,
149174
* on the given single-field index containing a date.

tests/GeospatialTest.php

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<?php
2+
3+
class GeospatialTest extends TestCase
4+
{
5+
public function setUp()
6+
{
7+
parent::setUp();
8+
9+
Schema::collection('locations', function ($collection) {
10+
$collection->geospatial('location', '2dsphere');
11+
});
12+
13+
Location::create([
14+
'name' => 'Picadilly',
15+
'location' => [
16+
'type' => 'LineString',
17+
'coordinates' => [
18+
[
19+
-0.1450383,
20+
51.5069158,
21+
],
22+
[
23+
-0.1367563,
24+
51.5100913,
25+
],
26+
[
27+
-0.1304123,
28+
51.5112908,
29+
],
30+
],
31+
],
32+
]);
33+
34+
Location::create([
35+
'name' => 'StJamesPalace',
36+
'location' => [
37+
'type' => 'Point',
38+
'coordinates' => [
39+
-0.139827,
40+
51.504736,
41+
],
42+
],
43+
]);
44+
}
45+
46+
public function tearDown()
47+
{
48+
Schema::drop('locations');
49+
}
50+
51+
public function testGeoWithin()
52+
{
53+
$locations = Location::where('location', 'geoWithin', [
54+
'$geometry' => [
55+
'type' => 'Polygon',
56+
'coordinates' => [[
57+
[
58+
-0.1450383,
59+
51.5069158,
60+
],
61+
[
62+
-0.1367563,
63+
51.5100913,
64+
],
65+
[
66+
-0.1270247,
67+
51.5013233,
68+
],
69+
[
70+
-0.1460866,
71+
51.4952136,
72+
],
73+
[
74+
-0.1450383,
75+
51.5069158,
76+
],
77+
]],
78+
],
79+
]);
80+
81+
$this->assertEquals(1, $locations->count());
82+
83+
$locations->get()->each(function ($item, $key) {
84+
$this->assertEquals('StJamesPalace', $item->name);
85+
});
86+
}
87+
88+
public function testGeoIntersects()
89+
{
90+
$locations = Location::where('location', 'geoIntersects', [
91+
'$geometry' => [
92+
'type' => 'LineString',
93+
'coordinates' => [
94+
[
95+
-0.144044,
96+
51.515215,
97+
],
98+
[
99+
-0.1367563,
100+
51.5100913,
101+
],
102+
[
103+
-0.129545,
104+
51.5078646,
105+
],
106+
],
107+
]
108+
]);
109+
110+
$this->assertEquals(1, $locations->count());
111+
112+
$locations->get()->each(function ($item, $key) {
113+
$this->assertEquals('Picadilly', $item->name);
114+
});
115+
}
116+
117+
public function testNear()
118+
{
119+
$locations = Location::where('location', 'near', [
120+
'$geometry' => [
121+
'type' => 'Point',
122+
'coordinates' => [
123+
-0.1367563,
124+
51.5100913,
125+
],
126+
],
127+
'$maxDistance' => 50,
128+
]);
129+
130+
$locations = $locations->get();
131+
132+
$this->assertEquals(1, $locations->count());
133+
134+
$locations->each(function ($item, $key) {
135+
$this->assertEquals('Picadilly', $item->name);
136+
});
137+
}
138+
}

tests/SchemaTest.php

+18
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,24 @@ public function testFluent()
167167
$this->assertEquals(1, $index['key']['token']);
168168
}
169169

170+
public function testGeospatial()
171+
{
172+
Schema::collection('newcollection', function ($collection) {
173+
$collection->geospatial('point');
174+
$collection->geospatial('area', '2d');
175+
$collection->geospatial('continent', '2dsphere');
176+
});
177+
178+
$index = $this->getIndex('newcollection', 'point');
179+
$this->assertEquals('2d', $index['key']['point']);
180+
181+
$index = $this->getIndex('newcollection', 'area');
182+
$this->assertEquals('2d', $index['key']['area']);
183+
184+
$index = $this->getIndex('newcollection', 'continent');
185+
$this->assertEquals('2dsphere', $index['key']['continent']);
186+
}
187+
170188
public function testDummies()
171189
{
172190
Schema::collection('newcollection', function ($collection) {

tests/models/Location.php

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
4+
5+
class Location extends Eloquent
6+
{
7+
protected $collection = 'locations';
8+
protected static $unguarded = true;
9+
}

0 commit comments

Comments
 (0)