В 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 может быть настолько простым.