Skip to content

Commit e961af4

Browse files
committed
ADDED: geo search
1 parent c8cfa91 commit e961af4

File tree

5 files changed

+140
-5
lines changed

5 files changed

+140
-5
lines changed

README.md

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

80-
```/storage/*.index```
81+
```
82+
/storage/*.index
83+
/storage/*.geoindex
84+
```
8185

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

src/Console/ImportCommand.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Contracts\Events\Dispatcher;
77
use TeamTNT\TNTSearch\TNTSearch;
88
use Illuminate\Support\Facades\Schema;
9+
use TeamTNT\TNTSearch\TNTGeoSearch;
910

1011
class ImportCommand extends Command
1112
{
@@ -66,6 +67,23 @@ public function handle(Dispatcher $events)
6667
$indexer->query($query->toSql());
6768

6869
$indexer->run();
70+
71+
if (!empty($config['geoIndex'])) {
72+
$geotnt = new TNTGeoSearch();
73+
74+
$geotnt->loadConfig($config);
75+
$geotnt->setDatabaseHandle($db->getPdo());
76+
77+
$geoIndexer = $geotnt->getIndex();
78+
$geoIndexer->loadConfig($geotnt->config);
79+
$geoIndexer->createIndex($model->searchableAs().'.geoindex');
80+
$geoIndexer->setPrimaryKey($model->getKeyName());
81+
82+
$geoIndexer->query($query->toSql());
83+
84+
$geoIndexer->run();
85+
}
86+
6987
$this->info('All ['.$class.'] records have been imported.');
7088
}
7189
}

src/Engines/TNTSearchEngine.php

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use TeamTNT\Scout\Events\SearchPerformed;
1111
use TeamTNT\TNTSearch\Exceptions\IndexNotFoundException;
1212
use TeamTNT\TNTSearch\TNTSearch;
13+
use TeamTNT\TNTSearch\TNTGeoSearch;
1314

1415
class TNTSearchEngine extends Engine
1516
{
@@ -25,14 +26,20 @@ class TNTSearchEngine extends Engine
2526
*/
2627
protected $builder;
2728

29+
/**
30+
* @var TNTGeoSearch
31+
*/
32+
protected $geotnt;
33+
2834
/**
2935
* Create a new engine instance.
3036
*
3137
* @param TNTSearch $tnt
3238
*/
33-
public function __construct(TNTSearch $tnt)
39+
public function __construct(TNTSearch $tnt, TNTGeoSearch $geotnt = null)
3440
{
3541
$this->tnt = $tnt;
42+
$this->geotnt = $geotnt;
3643
}
3744

3845
public function getTNT()
@@ -54,21 +61,52 @@ public function update($models)
5461
$index = $this->tnt->getIndex();
5562
$index->setPrimaryKey($models->first()->getKeyName());
5663

64+
$geoindex = null;
65+
if ($this->geotnt) {
66+
$this->geotnt->selectIndex("{$models->first()->searchableAs()}.geoindex");
67+
$geoindex = $this->geotnt->getIndex();
68+
$geoindex->loadConfig($this->geotnt->config);
69+
$geoindex->setPrimaryKey($models->first()->getKeyName());
70+
$geoindex->indexBeginTransaction();
71+
}
72+
5773
$index->indexBeginTransaction();
58-
$models->each(function ($model) use ($index) {
74+
$models->each(function ($model) use ($index, $geoindex) {
5975
$array = $model->toSearchableArray();
6076

6177
if (empty($array)) {
6278
return;
6379
}
6480

81+
if ($geoindex) {
82+
$latitude = isset($array['latitude']) ? (float) $array['latitude'] : null;
83+
$longitude = isset($array['longitude']) ? (float) $array['longitude'] : null;
84+
unset($array['longitude']);
85+
unset($array['latitude']);
86+
}
87+
6588
if ($model->getKey()) {
6689
$index->update($model->getKey(), $array);
90+
91+
if ($geoindex) {
92+
$geoindex->prepareAndExecuteStatement(
93+
'DELETE FROM locations WHERE doc_id = :documentId;',
94+
[['key' => ':documentId', 'value' => $model->getKey()]]
95+
);
96+
}
6797
} else {
6898
$index->insert($array);
6999
}
100+
if ($geoindex && !empty($latitude) && !empty($longitude)) {
101+
$array['latitude'] = $latitude;
102+
$array['longitude'] = $longitude;
103+
$geoindex->insert($array);
104+
}
70105
});
71106
$index->indexEndTransaction();
107+
if ($this->geotnt) {
108+
$geoindex->indexEndTransaction();
109+
}
72110
}
73111

74112
/**
@@ -86,6 +124,17 @@ public function delete($models)
86124
$index = $this->tnt->getIndex();
87125
$index->setPrimaryKey($model->getKeyName());
88126
$index->delete($model->getKey());
127+
128+
if ($this->geotnt) {
129+
$this->geotnt->selectIndex("{$model->searchableAs()}.geoindex");
130+
$index = $this->geotnt->getIndex();
131+
$index->loadConfig($this->geotnt->config);
132+
$index->setPrimaryKey($model->getKeyName());
133+
$index->prepareAndExecuteStatement(
134+
'DELETE FROM locations WHERE doc_id = :documentId;',
135+
[['key' => ':documentId', 'value' => $model->getKey()]]
136+
);
137+
}
89138
});
90139
}
91140

@@ -154,6 +203,10 @@ protected function performSearch(Builder $builder, array $options = [])
154203
$limit = $builder->limit ?: 10000;
155204
$this->tnt->selectIndex("{$index}.index");
156205

206+
if ($this->geotnt) {
207+
$this->geotnt->selectIndex("{$index}.geoindex");
208+
}
209+
157210
$this->builder = $builder;
158211

159212
if (isset($builder->model->asYouType)) {
@@ -169,6 +222,13 @@ protected function performSearch(Builder $builder, array $options = [])
169222
);
170223
}
171224

225+
if (is_array($builder->query)) {
226+
$location = $builder->query['location'];
227+
$distance = $builder->query['distance'];
228+
$limit = array_key_exists('limit', $builder->query) ? $builder->query['limit'] : 10000;
229+
return $this->geotnt->findNearest($location, $distance, $limit);
230+
}
231+
172232
$builder->query = $this->applyFilters('query_expansion', $builder->query, get_class($builder->model));
173233

174234
if (isset($this->tnt->config['searchBoolean']) ? $this->tnt->config['searchBoolean'] : false) {
@@ -181,6 +241,7 @@ protected function performSearch(Builder $builder, array $options = [])
181241
event(new SearchPerformed($builder, $res));
182242
return $res;
183243
}
244+
184245
}
185246

186247
/**
@@ -276,6 +337,14 @@ public function initIndex($model)
276337
$indexer->setDatabaseHandle($model->getConnection()->getPdo());
277338
$indexer->setPrimaryKey($model->getKeyName());
278339
}
340+
341+
if ($this->geotnt && !file_exists($this->tnt->config['storage']."/{$indexName}.geoindex")) {
342+
$indexer = $this->geotnt->getIndex();
343+
$indexer->loadConfig($this->geotnt->config);
344+
$indexer->createIndex("$indexName.geoindex");
345+
$indexer->setDatabaseHandle($model->getConnection()->getPdo());
346+
$indexer->setPrimaryKey($model->getKeyName());
347+
}
279348
}
280349

281350
/**
@@ -417,6 +486,13 @@ public function flush($model)
417486
if (file_exists($pathToIndex)) {
418487
unlink($pathToIndex);
419488
}
489+
490+
if ($this->geotnt){
491+
$pathToGeoIndex = $this->geotnt->config['storage']."/{$indexName}.geoindex";
492+
if (file_exists($pathToGeoIndex)) {
493+
unlink($pathToGeoIndex);
494+
}
495+
}
420496
}
421497

422498
/**

src/TNTSearchScoutServiceProvider.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,15 @@ public function boot()
3030
$this->setFuzziness($tnt);
3131
$this->setAsYouType($tnt);
3232

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

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

tests/TNTSearchEngineTest.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,36 @@ public function test_update_adds_objects_to_index()
3333
$index->shouldReceive('update');
3434
$index->shouldReceive('indexEndTransaction');
3535

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

0 commit comments

Comments
 (0)