Skip to content

Commit 819caf8

Browse files
committed
Add GeoSearch Index and search
1 parent 3e95445 commit 819caf8

File tree

5 files changed

+145
-11
lines changed

5 files changed

+145
-11
lines changed

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,16 @@ In your `config/scout.php` add:
7171
],
7272
'asYouType' => false,
7373
'searchBoolean' => env('TNTSEARCH_BOOLEAN', false),
74+
'geoIndex' => env('TNTSEARCH_GEOINDEX', false),
7475
],
7576
```
7677
To prevent your search indexes being commited to your project repository,
7778
add the following line to your `.gitignore` file.
7879

79-
```/storage/*.index```
80+
```
81+
/storage/*.index
82+
/storage/*.geoindex
83+
```
8084

8185
The `asYouType` option can be set per model basis, see the example below.
8286

src/Console/ImportCommand.php

+24-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Console\Command;
66
use Illuminate\Contracts\Events\Dispatcher;
7+
use TeamTNT\TNTSearch\TNTGeoSearch;
78
use TeamTNT\TNTSearch\TNTSearch;
89
use Illuminate\Support\Facades\Schema;
910

@@ -40,12 +41,6 @@ public function handle(Dispatcher $events)
4041
$config = config('scout.tntsearch') + config("database.connections.$driver");
4142
$db = app('db')->connection($driver);
4243

43-
$tnt->loadConfig($config);
44-
$tnt->setDatabaseHandle($db->getPdo());
45-
46-
$indexer = $tnt->createIndex($model->searchableAs().'.index');
47-
$indexer->setPrimaryKey($model->getKeyName());
48-
4944
$availableColumns = Schema::connection($driver)->getColumnListing($model->getTable());
5045
$desiredColumns = array_keys($model->toSearchableArray());
5146

@@ -58,9 +53,32 @@ public function handle(Dispatcher $events)
5853
->addSelect($fields);
5954
}
6055

56+
$tnt->loadConfig($config);
57+
$tnt->setDatabaseHandle($db->getPdo());
58+
59+
$indexer = $tnt->createIndex($model->searchableAs().'.index');
60+
$indexer->setPrimaryKey($model->getKeyName());
61+
6162
$indexer->query($query->toSql());
6263

6364
$indexer->run();
65+
66+
if (!empty($config['geoIndex'])) {
67+
$geotnt = new TNTGeoSearch();
68+
69+
$geotnt->loadConfig($config);
70+
$geotnt->setDatabaseHandle($db->getPdo());
71+
72+
$geoIndexer = $geotnt->getIndex();
73+
$geoIndexer->loadConfig($geotnt->config);
74+
$geoIndexer->createIndex($model->searchableAs().'.geoindex');
75+
$geoIndexer->setPrimaryKey($model->getKeyName());
76+
77+
$geoIndexer->query($query->toSql());
78+
79+
$geoIndexer->run();
80+
}
81+
6482
$this->info('All ['.$class.'] records have been imported.');
6583
}
6684
}

src/Engines/TNTSearchEngine.php

+76-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Laravel\Scout\Builder;
99
use Laravel\Scout\Engines\Engine;
1010
use TeamTNT\TNTSearch\Exceptions\IndexNotFoundException;
11+
use TeamTNT\TNTSearch\TNTGeoSearch;
1112
use TeamTNT\TNTSearch\TNTSearch;
1213

1314
class TNTSearchEngine extends Engine
@@ -17,6 +18,11 @@ class TNTSearchEngine extends Engine
1718
*/
1819
protected $tnt;
1920

21+
/**
22+
* @var TNTGeoSearch
23+
*/
24+
protected $geotnt;
25+
2026
/**
2127
* @var Builder
2228
*/
@@ -26,10 +32,12 @@ class TNTSearchEngine extends Engine
2632
* Create a new engine instance.
2733
*
2834
* @param TNTSearch $tnt
35+
* @param TNTGeoSearch|null $geotnt
2936
*/
30-
public function __construct(TNTSearch $tnt)
37+
public function __construct(TNTSearch $tnt, TNTGeoSearch $geotnt = null)
3138
{
3239
$this->tnt = $tnt;
40+
$this->geotnt = $geotnt;
3341
}
3442

3543
/**
@@ -46,21 +54,51 @@ public function update($models)
4654
$index = $this->tnt->getIndex();
4755
$index->setPrimaryKey($models->first()->getKeyName());
4856

57+
$geoindex = null;
58+
if ($this->geotnt) {
59+
$this->geotnt->selectIndex("{$models->first()->searchableAs()}.geoindex");
60+
$geoindex = $this->geotnt->getIndex();
61+
$geoindex->loadConfig($this->geotnt->config);
62+
$geoindex->setPrimaryKey($models->first()->getKeyName());
63+
$geoindex->indexBeginTransaction();
64+
}
65+
4966
$index->indexBeginTransaction();
50-
$models->each(function ($model) use ($index) {
67+
$models->each(function ($model) use ($index, $geoindex) {
5168
$array = $model->toSearchableArray();
5269

5370
if (empty($array)) {
5471
return;
5572
}
5673

74+
if ($geoindex) {
75+
$latitude = isset($array['latitude']) ? (float) $array['latitude'] : null;
76+
$longitude = isset($array['longitude']) ? (float) $array['longitude'] : null;
77+
unset($array['longitude']);
78+
unset($array['latitude']);
79+
}
80+
5781
if ($model->getKey()) {
5882
$index->update($model->getKey(), $array);
83+
if ($geoindex) {
84+
$geoindex->prepareAndExecuteStatement(
85+
'DELETE FROM locations WHERE doc_id = :documentId;',
86+
[['key' => ':documentId', 'value' => $model->getKey()]]
87+
);
88+
}
5989
} else {
6090
$index->insert($array);
6191
}
92+
if ($geoindex && !empty($latitude) && !empty($longitude)) {
93+
$array['latitude'] = $latitude;
94+
$array['longitude'] = $longitude;
95+
$geoindex->insert($array);
96+
}
6297
});
6398
$index->indexEndTransaction();
99+
if ($this->geotnt) {
100+
$geoindex->indexEndTransaction();
101+
}
64102
}
65103

66104
/**
@@ -78,6 +116,17 @@ public function delete($models)
78116
$index = $this->tnt->getIndex();
79117
$index->setPrimaryKey($model->getKeyName());
80118
$index->delete($model->getKey());
119+
120+
if ($this->geotnt) {
121+
$this->geotnt->selectIndex("{$model->searchableAs()}.geoindex");
122+
$index = $this->geotnt->getIndex();
123+
$index->loadConfig($this->geotnt->config);
124+
$index->setPrimaryKey($model->getKeyName());
125+
$index->prepareAndExecuteStatement(
126+
'DELETE FROM locations WHERE doc_id = :documentId;',
127+
[['key' => ':documentId', 'value' => $model->getKey()]]
128+
);
129+
}
81130
});
82131
}
83132

@@ -145,6 +194,9 @@ protected function performSearch(Builder $builder, array $options = [])
145194
$index = $builder->index ?: $builder->model->searchableAs();
146195
$limit = $builder->limit ?: 10000;
147196
$this->tnt->selectIndex("{$index}.index");
197+
if ($this->geotnt) {
198+
$this->geotnt->selectIndex("{$index}.geoindex");
199+
}
148200

149201
$this->builder = $builder;
150202

@@ -160,6 +212,14 @@ protected function performSearch(Builder $builder, array $options = [])
160212
$options
161213
);
162214
}
215+
216+
if (is_array($builder->query)) {
217+
$location = $builder->query['location'];
218+
$distance = $builder->query['distance'];
219+
$limit = array_key_exists('limit', $builder->query) ? $builder->query['limit'] : 10000;
220+
return $this->geotnt->findNearest($location, $distance, $limit);
221+
}
222+
163223
if (isset($this->tnt->config['searchBoolean']) ? $this->tnt->config['searchBoolean'] : false) {
164224
return $this->tnt->searchBoolean($builder->query, $limit);
165225
} else {
@@ -260,6 +320,13 @@ public function initIndex($model)
260320
$indexer->setDatabaseHandle($model->getConnection()->getPdo());
261321
$indexer->setPrimaryKey($model->getKeyName());
262322
}
323+
if ($this->geotnt && !file_exists($this->tnt->config['storage']."/{$indexName}.geoindex")) {
324+
$indexer = $this->geotnt->getIndex();
325+
$indexer->loadConfig($this->geotnt->config);
326+
$indexer->createIndex("$indexName.geoindex");
327+
$indexer->setDatabaseHandle($model->getConnection()->getPdo());
328+
$indexer->setPrimaryKey($model->getKeyName());
329+
}
263330
}
264331

265332
/**
@@ -401,5 +468,12 @@ public function flush($model)
401468
if (file_exists($pathToIndex)) {
402469
unlink($pathToIndex);
403470
}
471+
472+
if ($this->geotnt){
473+
$pathToGeoIndex = $this->geotnt->config['storage']."/{$indexName}.geoindex";
474+
if (file_exists($pathToGeoIndex)) {
475+
unlink($pathToGeoIndex);
476+
}
477+
}
404478
}
405479
}

src/TNTSearchScoutServiceProvider.php

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php namespace TeamTNT\Scout;
22

3+
use TeamTNT\TNTSearch\TNTGeoSearch;
34
use TeamTNT\TNTSearch\TNTSearch;
45
use Laravel\Scout\EngineManager;
56
use Laravel\Scout\Builder;
@@ -28,7 +29,15 @@ public function boot()
2829
$this->setFuzziness($tnt);
2930
$this->setAsYouType($tnt);
3031

31-
return new TNTSearchEngine($tnt);
32+
$geotnt = null;
33+
if (!empty($config['geoIndex'])) {
34+
$geotnt = new TNTGeoSearch();
35+
36+
$geotnt->loadConfig($config);
37+
$geotnt->setDatabaseHandle(app('db')->connection()->getPdo());
38+
}
39+
40+
return new TNTSearchEngine($tnt, $geotnt);
3241
});
3342

3443
if ($this->app->runningInConsole()) {

tests/TNTSearchEngineTest.php

+30-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,36 @@ public function test_update_adds_objects_to_index()
2929
$index->shouldReceive('update');
3030
$index->shouldReceive('indexEndTransaction');
3131

32-
$engine = new TNTSearchEngine($client);
32+
$geoClient = Mockery::mock('TeamTNT\TNTSearch\TNTGeoSearch');
33+
34+
$config = [
35+
'storage' => '/',
36+
'fuzziness' => false,
37+
'fuzzy' => [
38+
'prefix_length' => 2,
39+
'max_expansions' => 50,
40+
'distance' => 2
41+
],
42+
'asYouType' => false,
43+
'searchBoolean' => false,
44+
];
45+
46+
$geoClient->shouldReceive('getIndex')
47+
->andReturn($geoIndex = Mockery::mock('TeamTNT\TNTSearch\Indexer\TNTGeoIndexer'))
48+
->andSet('config', $config);
49+
$geoIndex->shouldReceive('loadConfig');
50+
$geoIndex->shouldReceive('createIndex')
51+
->with('table.geoindex');
52+
$geoIndex->shouldReceive('setDatabaseHandle');
53+
$geoIndex->shouldReceive('setPrimaryKey');
54+
$geoClient->shouldReceive('selectIndex');
55+
$geoIndex->shouldReceive('indexBeginTransaction');
56+
$geoIndex->shouldReceive('prepareAndExecuteStatement');
57+
$geoIndex->shouldReceive('insert');
58+
$geoIndex->shouldReceive('indexEndTransaction');
59+
60+
61+
$engine = new TNTSearchEngine($client, $geoClient);
3362
$engine->update(Collection::make([new TNTSearchEngineTestModel()]));
3463
}
3564
}

0 commit comments

Comments
 (0)