В Lucene недавно было сделано несколько незаметных улучшений, которые в совокупности сделали неожиданно простым добавление геопространственной дистанции в любое поисковое приложение Lucene, например:
1
2
3
|
< 1 km ( 147 ) < 2 km ( 579 ) < 5 km ( 2775 ) |
Такие дистанционные грани, которые позволяют пользователю быстро фильтровать свои результаты поиска по тем, которые находятся близко к их местоположению, стали особенно важными в последнее время, поскольку в настоящее время большинство запросов выполняется с мобильных смартфонов.
В прошлом это было сложно реализовать, потому что это очень динамично и дорого: количество фасетов зависит от местоположения каждого пользователя и поэтому не может быть кэшировано и разделено между пользователями, а базовая математика для пространственного расстояния сложна.
Но несколько недавних улучшений Lucene теперь делают это удивительно простым!
Во-первых, огранка динамического диапазона Lucene была обобщена для принятия любого значения ValueSource
, а не только числового поля значений документа из индекса. Благодаря недавно добавленному модулю выражений это означает, что вы можете предлагать фасеты динамического диапазона, вычисленные из произвольного выражения JavaScript , поскольку выражение компилируется на лету в ValueSource
с использованием пользовательских сгенерированных байт-кодов Java с ASM . Аспекты диапазона Lucene теперь также быстрее, используя деревья сегментов, чтобы быстро назначить каждое значение соответствующим диапазонам .
Во-вторых, функция расстояния Haversine была добавлена в модуль выражений. Реализация использует впечатляюще быстрое приближение к обычно дорогостоящим тригонометрическим функциям, частично отобранным из проекта Java Optimized Development Kit , не жертвуя слишком большой точностью. Маловероятно, что аппроксимации когда-либо будут иметь значение на практике, и существует открытая проблема для дальнейшего улучшения аппроксимации.
Внезапно, вооружившись этими улучшениями, если вы индексируете широту и долготу как DoubleDocValuesField
в каждом документе и знаете местоположение пользователя по широте / долготе для каждого запроса, вы можете легко вычислить количество фасетов и предложить детализацию по любому набору выбранных расстояний. ,
Сначала индексируйте ваши документы с помощью полей широты и долготы:
1
2
3
4
|
Document doc = new Document(); doc.add( new DoubleField( "latitude" , 40.759011 , Field.Store.NO)); doc.add( new DoubleField( "longitude" , - 73.9844722 , Field.Store.NO)); writer.addDocument(doc); |
Во время поиска получите ValueSource
путем создания динамического выражения, которое вызывает функцию Haversine:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
private ValueSource getDistanceValueSource() { Expression distance; try { distance = JavascriptCompiler.compile( "haversin(40.7143528,-74.0059731,latitude,longitude)" ); } catch (ParseException pe) { // Should not happen throw new RuntimeException(pe); } SimpleBindings bindings = new SimpleBindings(); bindings.add( new SortField( "latitude" , SortField.Type.DOUBLE)); bindings.add( new SortField( "longitude" , SortField.Type.DOUBLE)); return distance.getValueSource(bindings); } |
Вместо вышеуказанной широты / долготы, вы должны указать местоположение пользователя.
Используя этот ValueSource
, рассчитайте динамический подсчет фасетов следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
FacetsCollector fc = new FacetsCollector(); searcher.search( new MatchAllDocsQuery(), fc); Facets facets = new DoubleRangeFacetCounts( "field" , getDistanceValueSource(), fc, ONE_KM, TWO_KM, FIVE_KM, TEN_KM); return facets.getTopChildren( 10 , "field" ); |
Обычно вы используете «реальный» запрос вместо MatchAllDocsQuery
просмотра на верхнем уровне. Наконец, когда пользователь выберет расстояние для детализации, используйте метод Range.getFilter
и добавьте его в DrillDownQuery
используя ConstantScoreQuery
:
01
02
03
04
05
06
07
08
09
10
|
public TopDocs drillDown(DoubleRange range) throws IOException { // Passing no baseQuery means we drill down on all // documents ("browse only"): DrillDownQuery q = new DrillDownQuery( null ); q.add( "field" , new ConstantScoreQuery( range.getFilter(getDistanceValueSource()))); return searcher.search(q, 10 ); } |
Смотрите полный исходный код здесь , из модуля lucene/demo
.
Когда я впервые протестировал этот пример, произошла забавная ошибка , а затем были пересмотрены API-интерфейсы фасетов , так что вам нужно дождаться выпуска Lucene 4.7 или просто использовать текущие исходники 4.x , чтобы получить этот пример. за работой.
Хотя этот пример прост и работает правильно, возможны некоторые явные улучшения производительности, такие как использование ограничивающего прямоугольника в качестве быстрого соответствия, чтобы избежать вычисления Haversine для попаданий, которые явно находятся за пределами диапазона возможных углублений (исправлений) добро пожаловать!). Тем не менее, это хороший шаг вперед для огранки Lucene, и удивительно, что геопространственное огранение расстояния с Lucene может быть настолько простым.