11
11
import org .elasticsearch .index .query .functionscore .FunctionScoreQueryBuilder ;
12
12
import org .elasticsearch .index .query .functionscore .FunctionScoreQueryBuilder .FilterFunctionBuilder ;
13
13
import org .elasticsearch .index .query .functionscore .ScoreFunctionBuilders ;
14
- import org .elasticsearch .index .query .functionscore .ScriptScoreFunctionBuilder ;
15
- import org .elasticsearch .script .Script ;
16
- import org .elasticsearch .script .ScriptType ;
14
+ import org .elasticsearch .index .query .functionscore .WeightBuilder ;
17
15
18
16
import java .util .*;
19
17
34
32
* Created by Sachin Dole on 2/12/2015.
35
33
*/
36
34
public class PhotonQueryBuilder {
35
+ private static final String [] ALT_NAMES = new String []{"alt" , "int" , "loc" , "old" , "reg" , "housename" };
36
+
37
37
private FunctionScoreQueryBuilder finalQueryWithoutTagFilterBuilder ;
38
38
39
39
private BoolQueryBuilder queryBuilderForTopLevelFilter ;
@@ -50,27 +50,25 @@ public class PhotonQueryBuilder {
50
50
51
51
protected ArrayList <FilterFunctionBuilder > alFilterFunction4QueryBuilder = new ArrayList <>(1 );
52
52
53
- protected BoolQueryBuilder query4QueryBuilder ;
54
-
55
53
56
54
private PhotonQueryBuilder (String query , String language , List <String > languages , boolean lenient ) {
57
- query4QueryBuilder = QueryBuilders .boolQuery ();
55
+ BoolQueryBuilder query4QueryBuilder = QueryBuilders .boolQuery ();
58
56
57
+ // 1. All terms of the quey must be contained in the place record somehow. Be more lenient on second try.
58
+ QueryBuilder collectorQuery ;
59
59
if (lenient ) {
60
- BoolQueryBuilder builder = QueryBuilders .boolQuery ()
60
+ collectorQuery = QueryBuilders .boolQuery ()
61
61
.should (QueryBuilders .matchQuery ("collector.default" , query )
62
- .fuzziness (Fuzziness .ONE )
63
- .prefixLength (2 )
64
- .analyzer ("search_ngram" )
65
- .minimumShouldMatch ("-1" ))
62
+ .fuzziness (Fuzziness .ONE )
63
+ .prefixLength (2 )
64
+ .analyzer ("search_ngram" )
65
+ .minimumShouldMatch ("-1" ))
66
66
.should (QueryBuilders .matchQuery (String .format ("collector.%s.ngrams" , language ), query )
67
- .fuzziness (Fuzziness .ONE )
68
- .prefixLength (2 )
69
- .analyzer ("search_ngram" )
70
- .minimumShouldMatch ("-1" ))
67
+ .fuzziness (Fuzziness .ONE )
68
+ .prefixLength (2 )
69
+ .analyzer ("search_ngram" )
70
+ .minimumShouldMatch ("-1" ))
71
71
.minimumShouldMatch ("1" );
72
-
73
- query4QueryBuilder .must (builder );
74
72
} else {
75
73
MultiMatchQueryBuilder builder =
76
74
QueryBuilders .multiMatchQuery (query ).field ("collector.default" , 1.0f ).type (MultiMatchQueryBuilder .Type .CROSS_FIELDS ).prefixLength (2 ).analyzer ("search_ngram" ).minimumShouldMatch ("100%" );
@@ -79,31 +77,65 @@ private PhotonQueryBuilder(String query, String language, List<String> languages
79
77
builder .field (String .format ("collector.%s.ngrams" , lang ), lang .equals (language ) ? 1.0f : 0.6f );
80
78
}
81
79
82
- query4QueryBuilder . must ( builder ) ;
80
+ collectorQuery = builder ;
83
81
}
84
82
85
- query4QueryBuilder
86
- .should (QueryBuilders .matchQuery (String .format ("name.%s.raw" , language ), query ).boost (200 )
87
- .analyzer ("search_raw" ))
88
- .should (QueryBuilders .matchQuery (String .format ("collector.%s.raw" , language ), query ).boost (100 )
89
- .analyzer ("search_raw" ));
83
+ query4QueryBuilder .must (collectorQuery );
84
+
85
+ // 2. Prefer records that have the full names in. For address records with housenumbers this is the main
86
+ // filter creterion because they have no name. Therefore boost the score in this case.
87
+ MultiMatchQueryBuilder hnrQuery = QueryBuilders .multiMatchQuery (query )
88
+ .field ("collector.default.raw" , 1.0f )
89
+ .type (MultiMatchQueryBuilder .Type .BEST_FIELDS );
90
+
91
+ for (String lang : languages ) {
92
+ hnrQuery .field (String .format ("collector.%s.raw" , lang ), lang .equals (language ) ? 1.0f : 0.6f );
93
+ }
94
+
95
+ query4QueryBuilder .should (QueryBuilders .functionScoreQuery (hnrQuery .boost (0.3f ), new FilterFunctionBuilder []{
96
+ new FilterFunctionBuilder (QueryBuilders .matchQuery ("housenumber" , query ).analyzer ("standard" ), new WeightBuilder ().setWeight (10f ))
97
+ }));
98
+
99
+ // 3. Either the name or housenumber must be in the query terms.
100
+ String defLang = "default" .equals (language ) ? languages .get (0 ) : language ;
101
+ MultiMatchQueryBuilder nameNgramQuery = QueryBuilders .multiMatchQuery (query )
102
+ .type (MultiMatchQueryBuilder .Type .BEST_FIELDS )
103
+ .fuzziness (lenient ? Fuzziness .ONE : Fuzziness .ZERO )
104
+ .analyzer ("search_ngram" );
105
+
106
+ for (String lang : languages ) {
107
+ nameNgramQuery .field (String .format ("name.%s.ngrams" , lang ), lang .equals (defLang ) ? 1.0f : 0.4f );
108
+ }
90
109
91
- // this is former general-score, now inline
92
- String strCode = "double score = 1 + doc['importance'].value * 100; score" ;
93
- ScriptScoreFunctionBuilder functionBuilder4QueryBuilder =
94
- ScoreFunctionBuilders .scriptFunction (new Script (ScriptType .INLINE , "painless" , strCode , new HashMap <String , Object >()));
110
+ for (String alt : ALT_NAMES ) {
111
+ nameNgramQuery .field (String .format ("name.%s.raw" , alt ), 0.4f );
112
+ }
113
+
114
+ if (query .indexOf (',' ) < 0 && query .indexOf (' ' ) < 0 ) {
115
+ query4QueryBuilder .must (nameNgramQuery .boost (2f ));
116
+ } else {
117
+ query4QueryBuilder .must (QueryBuilders .boolQuery ()
118
+ .should (nameNgramQuery )
119
+ .should (QueryBuilders .matchQuery ("housenumber" , query ).analyzer ("standard" ))
120
+ .minimumShouldMatch ("1" ));
121
+ }
122
+
123
+ // 4. Rerank results for having the full name in the default language.
124
+ query4QueryBuilder
125
+ .should (QueryBuilders .matchQuery (String .format ("name.%s.raw" , language ), query ));
95
126
96
- alFilterFunction4QueryBuilder .add (new FilterFunctionBuilder (functionBuilder4QueryBuilder ));
97
127
98
- finalQueryWithoutTagFilterBuilder = new FunctionScoreQueryBuilder (query4QueryBuilder , alFilterFunction4QueryBuilder .toArray (new FilterFunctionBuilder [0 ]))
99
- .boostMode (CombineFunction .MULTIPLY ).scoreMode (ScoreMode .MULTIPLY );
128
+ // Weigh the resulting score by importance. Use a linear scale function that ensures that the weight
129
+ // never drops to 0 and cancels out the ES score.
130
+ finalQueryWithoutTagFilterBuilder = QueryBuilders .functionScoreQuery (query4QueryBuilder , new FilterFunctionBuilder []{
131
+ new FilterFunctionBuilder (ScoreFunctionBuilders .linearDecayFunction ("importance" , "1.0" , "0.6" ))
132
+ });
100
133
101
- // @formatter:off
134
+ // Filter for later: records that have a housenumber and no name must only appear when the housenumber matches.
102
135
queryBuilderForTopLevelFilter = QueryBuilders .boolQuery ()
103
136
.should (QueryBuilders .boolQuery ().mustNot (QueryBuilders .existsQuery ("housenumber" )))
104
137
.should (QueryBuilders .matchQuery ("housenumber" , query ).analyzer ("standard" ))
105
138
.should (QueryBuilders .existsQuery (String .format ("name.%s.raw" , language )));
106
- // @formatter:on
107
139
108
140
state = State .PLAIN ;
109
141
}
@@ -120,21 +152,27 @@ public static PhotonQueryBuilder builder(String query, String language, List<Str
120
152
return new PhotonQueryBuilder (query , language , languages , lenient );
121
153
}
122
154
123
- public PhotonQueryBuilder withLocationBias (Point point , double scale ) {
124
- if (point == null ) return this ;
155
+ public PhotonQueryBuilder withLocationBias (Point point , double scale , int zoom ) {
156
+ if (point == null || zoom < 4 ) return this ;
157
+
158
+ if (zoom > 18 ) {
159
+ zoom = 18 ;
160
+ }
161
+ double radius = (1 << (18 - zoom )) * 0.25 ;
162
+
163
+ if (scale <= 0.0 ) {
164
+ scale = 0.0000001 ;
165
+ }
166
+
125
167
Map <String , Object > params = newHashMap ();
126
168
params .put ("lon" , point .getX ());
127
169
params .put ("lat" , point .getY ());
128
170
129
- scale = Math .abs (scale );
130
- String strCode = "double dist = doc['coordinate'].planeDistance(params.lat, params.lon); " +
131
- "double score = 0.1 + " + scale + " / (1.0 + dist * 0.001 / 10.0); " +
132
- "score" ;
133
- ScriptScoreFunctionBuilder builder = ScoreFunctionBuilders .scriptFunction (new Script (ScriptType .INLINE , "painless" , strCode , params ));
134
- alFilterFunction4QueryBuilder .add (new FilterFunctionBuilder (builder ));
135
171
finalQueryWithoutTagFilterBuilder =
136
- new FunctionScoreQueryBuilder (query4QueryBuilder , alFilterFunction4QueryBuilder .toArray (new FilterFunctionBuilder [0 ]))
137
- .boostMode (CombineFunction .MULTIPLY );
172
+ QueryBuilders .functionScoreQuery (finalQueryWithoutTagFilterBuilder , new FilterFunctionBuilder [] {
173
+ new FilterFunctionBuilder (ScoreFunctionBuilders .exponentialDecayFunction ("coordinate" , params , radius + "km" , radius / 10 + "km" , 0.8 )),
174
+ new FilterFunctionBuilder (ScoreFunctionBuilders .linearDecayFunction ("importance" , "1.0" , scale ))
175
+ }).boostMode (CombineFunction .MULTIPLY ).scoreMode (ScoreMode .MAX );
138
176
return this ;
139
177
}
140
178
0 commit comments