Lucene.Net  3.0.3
Lucene.Net is a port of the Lucene search engine library, written in C# and targeted at .NET runtime users.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Properties Pages
BBoxStrategy.cs
Go to the documentation of this file.
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 using System;
19 using Lucene.Net.Documents;
20 using Lucene.Net.Index;
21 using Lucene.Net.Search;
22 using Lucene.Net.Search.Function;
23 using Lucene.Net.Spatial.Queries;
24 using Lucene.Net.Spatial.Util;
25 using Spatial4n.Core.Context;
26 using Spatial4n.Core.Shapes;
27 
28 namespace Lucene.Net.Spatial.BBox
29 {
30  public class BBoxStrategy : SpatialStrategy
31  {
32  public static String SUFFIX_MINX = "__minX";
33  public static String SUFFIX_MAXX = "__maxX";
34  public static String SUFFIX_MINY = "__minY";
35  public static String SUFFIX_MAXY = "__maxY";
36  public static String SUFFIX_XDL = "__xdl";
37 
38  /*
39  * The Bounding Box gets stored as four fields for x/y min/max and a flag
40  * that says if the box crosses the dateline (xdl).
41  */
42  public readonly String field_bbox;
43  public readonly String field_minX;
44  public readonly String field_minY;
45  public readonly String field_maxX;
46  public readonly String field_maxY;
47  public readonly String field_xdl; // crosses dateline
48 
49  public readonly double queryPower = 1.0;
50  public readonly double targetPower = 1.0f;
51  public int precisionStep = 8; // same as solr default
52 
53  public BBoxStrategy(SpatialContext ctx, String fieldNamePrefix)
54  : base(ctx, fieldNamePrefix)
55  {
56  field_bbox = fieldNamePrefix;
57  field_minX = fieldNamePrefix + SUFFIX_MINX;
58  field_maxX = fieldNamePrefix + SUFFIX_MAXX;
59  field_minY = fieldNamePrefix + SUFFIX_MINY;
60  field_maxY = fieldNamePrefix + SUFFIX_MAXY;
61  field_xdl = fieldNamePrefix + SUFFIX_XDL;
62  }
63 
64  public void SetPrecisionStep(int p)
65  {
66  precisionStep = p;
67  if (precisionStep <= 0 || precisionStep >= 64)
68  precisionStep = int.MaxValue;
69  }
70 
71  //---------------------------------
72  // Indexing
73  //---------------------------------
74 
75  public override AbstractField[] CreateIndexableFields(Shape shape)
76  {
77  var rect = shape as Rectangle;
78  if (rect != null)
79  return CreateIndexableFields(rect);
80  throw new InvalidOperationException("Can only index Rectangle, not " + shape);
81  }
82 
83  public AbstractField[] CreateIndexableFields(Rectangle bbox)
84  {
85  var fields = new AbstractField[5];
86  fields[0] = DoubleField(field_minX, bbox.GetMinX());
87  fields[1] = DoubleField(field_maxX, bbox.GetMaxX());
88  fields[2] = DoubleField(field_minY, bbox.GetMinY());
89  fields[3] = DoubleField(field_maxY, bbox.GetMaxY());
90  fields[4] = new Field(field_xdl, bbox.GetCrossesDateLine() ? "T" : "F", Field.Store.NO,
91  Field.Index.NOT_ANALYZED_NO_NORMS) {OmitNorms = true, OmitTermFreqAndPositions = true};
92  return fields;
93  }
94 
95  private AbstractField DoubleField(string field, double value)
96  {
97  var f = new NumericField(field, precisionStep, Field.Store.NO, true)
98  {OmitNorms = true, OmitTermFreqAndPositions = true};
99  f.SetDoubleValue(value);
100  return f;
101  }
102 
103  public override ValueSource MakeDistanceValueSource(Point queryPoint)
104  {
105  return new BBoxSimilarityValueSource(this, new DistanceSimilarity(this.GetSpatialContext(), queryPoint));
106  }
107 
108  public ValueSource MakeBBoxAreaSimilarityValueSource(Rectangle queryBox)
109  {
110  return new BBoxSimilarityValueSource(
111  this, new AreaSimilarity(queryBox, queryPower, targetPower));
112  }
113 
114  public override ConstantScoreQuery MakeQuery(SpatialArgs args)
115  {
116  return new ConstantScoreQuery(new QueryWrapperFilter(MakeSpatialQuery(args)));
117  }
118 
119  public Query MakeQueryWithValueSource(SpatialArgs args, ValueSource valueSource)
120  {
121 
122  var bq = new BooleanQuery();
123  var spatial = MakeFilter(args);
124  bq.Add(new ConstantScoreQuery(spatial), Occur.MUST);
125 
126  // This part does the scoring
127  Query spatialRankingQuery = new FunctionQuery(valueSource);
128  bq.Add(spatialRankingQuery, Occur.MUST);
129  return bq;
130  }
131 
132  public override Filter MakeFilter(SpatialArgs args)
133  {
134  return new QueryWrapperFilter(MakeSpatialQuery(args));
135  }
136 
137  private Query MakeSpatialQuery(SpatialArgs args)
138  {
139  var bbox = args.Shape as Rectangle;
140  if (bbox == null)
141  throw new InvalidOperationException("Can only query by Rectangle, not " + args.Shape);
142 
143  Query spatial = null;
144 
145  // Useful for understanding Relations:
146  // http://edndoc.esri.com/arcsde/9.1/general_topics/understand_spatial_relations.htm
147  SpatialOperation op = args.Operation;
148  if (op == SpatialOperation.BBoxIntersects) spatial = MakeIntersects(bbox);
149  else if (op == SpatialOperation.BBoxWithin) spatial = MakeWithin(bbox);
150  else if (op == SpatialOperation.Contains) spatial = MakeContains(bbox);
151  else if (op == SpatialOperation.Intersects) spatial = MakeIntersects(bbox);
152  else if (op == SpatialOperation.IsEqualTo) spatial = MakeEquals(bbox);
153  else if (op == SpatialOperation.IsDisjointTo) spatial = MakeDisjoint(bbox);
154  else if (op == SpatialOperation.IsWithin) spatial = MakeWithin(bbox);
155  else if (op == SpatialOperation.Overlaps) spatial = MakeIntersects(bbox);
156  else
157  {
158  throw new UnsupportedSpatialOperation(op);
159  }
160  return spatial;
161  }
162 
163  //-------------------------------------------------------------------------------
164  //
165  //-------------------------------------------------------------------------------
166 
167  /// <summary>
168  /// Constructs a query to retrieve documents that fully contain the input envelope.
169  /// </summary>
170  /// <param name="bbox"></param>
171  /// <returns>The spatial query</returns>
172  protected Query MakeContains(Rectangle bbox)
173  {
174 
175  // general case
176  // docMinX <= queryExtent.GetMinX() AND docMinY <= queryExtent.GetMinY() AND docMaxX >= queryExtent.GetMaxX() AND docMaxY >= queryExtent.GetMaxY()
177 
178  // Y conditions
179  // docMinY <= queryExtent.GetMinY() AND docMaxY >= queryExtent.GetMaxY()
180  Query qMinY = NumericRangeQuery.NewDoubleRange(field_minY, precisionStep, null, bbox.GetMinY(), false, true);
181  Query qMaxY = NumericRangeQuery.NewDoubleRange(field_maxY, precisionStep, bbox.GetMaxY(), null, true, false);
182  Query yConditions = this.MakeQuery(new Query[] { qMinY, qMaxY }, Occur.MUST);
183 
184  // X conditions
185  Query xConditions = null;
186 
187  // queries that do not cross the date line
188  if (!bbox.GetCrossesDateLine())
189  {
190 
191  // X Conditions for documents that do not cross the date line,
192  // documents that contain the min X and max X of the query envelope,
193  // docMinX <= queryExtent.GetMinX() AND docMaxX >= queryExtent.GetMaxX()
194  Query qMinX = NumericRangeQuery.NewDoubleRange(field_minX, precisionStep, null, bbox.GetMinX(), false, true);
195  Query qMaxX = NumericRangeQuery.NewDoubleRange(field_maxX, precisionStep, bbox.GetMaxX(), null, true, false);
196  Query qMinMax = this.MakeQuery(new Query[] { qMinX, qMaxX }, Occur.MUST);
197  Query qNonXDL = this.MakeXDL(false, qMinMax);
198 
199  // X Conditions for documents that cross the date line,
200  // the left portion of the document contains the min X of the query
201  // OR the right portion of the document contains the max X of the query,
202  // docMinXLeft <= queryExtent.GetMinX() OR docMaxXRight >= queryExtent.GetMaxX()
203  Query qXDLLeft = NumericRangeQuery.NewDoubleRange(field_minX, precisionStep, null, bbox.GetMinX(), false, true);
204  Query qXDLRight = NumericRangeQuery.NewDoubleRange(field_maxX, precisionStep, bbox.GetMaxX(), null, true, false);
205  Query qXDLLeftRight = this.MakeQuery(new Query[] { qXDLLeft, qXDLRight }, Occur.SHOULD);
206  Query qXDL = this.MakeXDL(true, qXDLLeftRight);
207 
208  // apply the non-XDL and XDL conditions
209  xConditions = this.MakeQuery(new Query[] { qNonXDL, qXDL }, Occur.SHOULD);
210 
211  // queries that cross the date line
212  }
213  else
214  {
215 
216  // No need to search for documents that do not cross the date line
217 
218  // X Conditions for documents that cross the date line,
219  // the left portion of the document contains the min X of the query
220  // AND the right portion of the document contains the max X of the query,
221  // docMinXLeft <= queryExtent.GetMinX() AND docMaxXRight >= queryExtent.GetMaxX()
222  Query qXDLLeft = NumericRangeQuery.NewDoubleRange(field_minX, precisionStep, null, bbox.GetMinX(), false, true);
223  Query qXDLRight = NumericRangeQuery.NewDoubleRange(field_maxX, precisionStep, bbox.GetMaxX(), null, true, false);
224  Query qXDLLeftRight = this.MakeQuery(new Query[] { qXDLLeft, qXDLRight }, Occur.MUST);
225 
226  xConditions = this.MakeXDL(true, qXDLLeftRight);
227  }
228 
229  // both X and Y conditions must occur
230  return this.MakeQuery(new Query[] { xConditions, yConditions }, Occur.MUST);
231  }
232 
233  /// <summary>
234  /// Constructs a query to retrieve documents that are disjoint to the input envelope.
235  /// </summary>
236  /// <param name="bbox"></param>
237  /// <returns>the spatial query</returns>
238  Query MakeDisjoint(Rectangle bbox)
239  {
240 
241  // general case
242  // docMinX > queryExtent.GetMaxX() OR docMaxX < queryExtent.GetMinX() OR docMinY > queryExtent.GetMaxY() OR docMaxY < queryExtent.GetMinY()
243 
244  // Y conditions
245  // docMinY > queryExtent.GetMaxY() OR docMaxY < queryExtent.GetMinY()
246  Query qMinY = NumericRangeQuery.NewDoubleRange(field_minY, precisionStep, bbox.GetMaxY(), null, false, false);
247  Query qMaxY = NumericRangeQuery.NewDoubleRange(field_maxY, precisionStep, null, bbox.GetMinY(), false, false);
248  Query yConditions = this.MakeQuery(new Query[] { qMinY, qMaxY }, Occur.SHOULD);
249 
250  // X conditions
251  Query xConditions = null;
252 
253  // queries that do not cross the date line
254  if (!bbox.GetCrossesDateLine())
255  {
256 
257  // X Conditions for documents that do not cross the date line,
258  // docMinX > queryExtent.GetMaxX() OR docMaxX < queryExtent.GetMinX()
259  Query qMinX = NumericRangeQuery.NewDoubleRange(field_minX, precisionStep, bbox.GetMaxX(), null, false, false);
260  Query qMaxX = NumericRangeQuery.NewDoubleRange(field_maxX, precisionStep, null, bbox.GetMinX(), false, false);
261  Query qMinMax = this.MakeQuery(new Query[] { qMinX, qMaxX }, Occur.SHOULD);
262  Query qNonXDL = this.MakeXDL(false, qMinMax);
263 
264  // X Conditions for documents that cross the date line,
265  // both the left and right portions of the document must be disjoint to the query
266  // (docMinXLeft > queryExtent.GetMaxX() OR docMaxXLeft < queryExtent.GetMinX()) AND
267  // (docMinXRight > queryExtent.GetMaxX() OR docMaxXRight < queryExtent.GetMinX())
268  // where: docMaxXLeft = 180.0, docMinXRight = -180.0
269  // (docMaxXLeft < queryExtent.GetMinX()) equates to (180.0 < queryExtent.GetMinX()) and is ignored
270  // (docMinXRight > queryExtent.GetMaxX()) equates to (-180.0 > queryExtent.GetMaxX()) and is ignored
271  Query qMinXLeft = NumericRangeQuery.NewDoubleRange(field_minX, precisionStep, bbox.GetMaxX(), null, false, false);
272  Query qMaxXRight = NumericRangeQuery.NewDoubleRange(field_maxX, precisionStep, null, bbox.GetMinX(), false, false);
273  Query qLeftRight = this.MakeQuery(new Query[] { qMinXLeft, qMaxXRight }, Occur.MUST);
274  Query qXDL = this.MakeXDL(true, qLeftRight);
275 
276  // apply the non-XDL and XDL conditions
277  xConditions = this.MakeQuery(new Query[] { qNonXDL, qXDL }, Occur.SHOULD);
278 
279  // queries that cross the date line
280  }
281  else
282  {
283 
284  // X Conditions for documents that do not cross the date line,
285  // the document must be disjoint to both the left and right query portions
286  // (docMinX > queryExtent.GetMaxX()Left OR docMaxX < queryExtent.GetMinX()) AND (docMinX > queryExtent.GetMaxX() OR docMaxX < queryExtent.GetMinX()Left)
287  // where: queryExtent.GetMaxX()Left = 180.0, queryExtent.GetMinX()Left = -180.0
288  Query qMinXLeft = NumericRangeQuery.NewDoubleRange(field_minX, precisionStep, 180.0, null, false, false);
289  Query qMaxXLeft = NumericRangeQuery.NewDoubleRange(field_maxX, precisionStep, null, bbox.GetMinX(), false, false);
290  Query qMinXRight = NumericRangeQuery.NewDoubleRange(field_minX, precisionStep, bbox.GetMaxX(), null, false, false);
291  Query qMaxXRight = NumericRangeQuery.NewDoubleRange(field_maxX, precisionStep, null, -180.0, false, false);
292  Query qLeft = this.MakeQuery(new Query[] { qMinXLeft, qMaxXLeft }, Occur.SHOULD);
293  Query qRight = this.MakeQuery(new Query[] { qMinXRight, qMaxXRight }, Occur.SHOULD);
294  Query qLeftRight = this.MakeQuery(new Query[] { qLeft, qRight }, Occur.MUST);
295 
296  // No need to search for documents that do not cross the date line
297 
298  xConditions = this.MakeXDL(false, qLeftRight);
299  }
300 
301  // either X or Y conditions should occur
302  return this.MakeQuery(new Query[] { xConditions, yConditions }, Occur.SHOULD);
303  }
304 
305  /*
306  * Constructs a query to retrieve documents that equal the input envelope.
307  *
308  * @return the spatial query
309  */
310  public Query MakeEquals(Rectangle bbox)
311  {
312 
313  // docMinX = queryExtent.GetMinX() AND docMinY = queryExtent.GetMinY() AND docMaxX = queryExtent.GetMaxX() AND docMaxY = queryExtent.GetMaxY()
314  Query qMinX = NumericRangeQuery.NewDoubleRange(field_minX, precisionStep, bbox.GetMinX(), bbox.GetMinX(), true, true);
315  Query qMinY = NumericRangeQuery.NewDoubleRange(field_minY, precisionStep, bbox.GetMinY(), bbox.GetMinY(), true, true);
316  Query qMaxX = NumericRangeQuery.NewDoubleRange(field_maxX, precisionStep, bbox.GetMaxX(), bbox.GetMaxX(), true, true);
317  Query qMaxY = NumericRangeQuery.NewDoubleRange(field_maxY, precisionStep, bbox.GetMaxY(), bbox.GetMaxY(), true, true);
318 
319  var bq = new BooleanQuery
320  {
321  {qMinX, Occur.MUST},
322  {qMinY, Occur.MUST},
323  {qMaxX, Occur.MUST},
324  {qMaxY, Occur.MUST}
325  };
326  return bq;
327  }
328 
329  /// <summary>
330  /// Constructs a query to retrieve documents that intersect the input envelope.
331  /// </summary>
332  /// <param name="bbox"></param>
333  /// <returns>the spatial query</returns>
334  Query MakeIntersects(Rectangle bbox)
335  {
336 
337  // the original intersects query does not work for envelopes that cross the date line,
338  // switch to a NOT Disjoint query
339 
340  // MUST_NOT causes a problem when it's the only clause type within a BooleanQuery,
341  // to get round it we add all documents as a SHOULD
342 
343  // there must be an envelope, it must not be disjoint
344  Query qDisjoint = MakeDisjoint(bbox);
345  Query qIsNonXDL = this.MakeXDL(false);
346  Query qIsXDL = this.MakeXDL(true);
347  Query qHasEnv = this.MakeQuery(new Query[] { qIsNonXDL, qIsXDL }, Occur.SHOULD);
348  var qNotDisjoint = new BooleanQuery {{qHasEnv, Occur.MUST}, {qDisjoint, Occur.MUST_NOT}};
349 
350  //Query qDisjoint = makeDisjoint();
351  //BooleanQuery qNotDisjoint = new BooleanQuery();
352  //qNotDisjoint.add(new MatchAllDocsQuery(),BooleanClause.Occur.SHOULD);
353  //qNotDisjoint.add(qDisjoint,BooleanClause.Occur.MUST_NOT);
354  return qNotDisjoint;
355  }
356 
357  /*
358  * Makes a boolean query based upon a collection of queries and a logical operator.
359  *
360  * @param queries the query collection
361  * @param occur the logical operator
362  * @return the query
363  */
364  BooleanQuery MakeQuery(Query[] queries, Occur occur)
365  {
366  var bq = new BooleanQuery();
367  foreach (Query query in queries)
368  {
369  bq.Add(query, occur);
370  }
371  return bq;
372  }
373 
374  /*
375  * Constructs a query to retrieve documents are fully within the input envelope.
376  *
377  * @return the spatial query
378  */
379  Query MakeWithin(Rectangle bbox)
380  {
381 
382  // general case
383  // docMinX >= queryExtent.GetMinX() AND docMinY >= queryExtent.GetMinY() AND docMaxX <= queryExtent.GetMaxX() AND docMaxY <= queryExtent.GetMaxY()
384 
385  // Y conditions
386  // docMinY >= queryExtent.GetMinY() AND docMaxY <= queryExtent.GetMaxY()
387  Query qMinY = NumericRangeQuery.NewDoubleRange(field_minY, precisionStep, bbox.GetMinY(), null, true, false);
388  Query qMaxY = NumericRangeQuery.NewDoubleRange(field_maxY, precisionStep, null, bbox.GetMaxY(), false, true);
389  Query yConditions = this.MakeQuery(new Query[] { qMinY, qMaxY }, Occur.MUST);
390 
391  // X conditions
392  Query xConditions = null;
393 
394  // X Conditions for documents that cross the date line,
395  // the left portion of the document must be within the left portion of the query,
396  // AND the right portion of the document must be within the right portion of the query
397  // docMinXLeft >= queryExtent.GetMinX() AND docMaxXLeft <= 180.0
398  // AND docMinXRight >= -180.0 AND docMaxXRight <= queryExtent.GetMaxX()
399  Query qXDLLeft = NumericRangeQuery.NewDoubleRange(field_minX, precisionStep, bbox.GetMinX(), null, true, false);
400  Query qXDLRight = NumericRangeQuery.NewDoubleRange(field_maxX, precisionStep, null, bbox.GetMaxX(), false, true);
401  Query qXDLLeftRight = this.MakeQuery(new Query[] { qXDLLeft, qXDLRight }, Occur.MUST);
402  Query qXDL = this.MakeXDL(true, qXDLLeftRight);
403 
404  // queries that do not cross the date line
405  if (!bbox.GetCrossesDateLine())
406  {
407 
408  // X Conditions for documents that do not cross the date line,
409  // docMinX >= queryExtent.GetMinX() AND docMaxX <= queryExtent.GetMaxX()
410  Query qMinX = NumericRangeQuery.NewDoubleRange(field_minX, precisionStep, bbox.GetMinX(), null, true, false);
411  Query qMaxX = NumericRangeQuery.NewDoubleRange(field_maxX, precisionStep, null, bbox.GetMaxX(), false, true);
412  Query qMinMax = this.MakeQuery(new Query[] { qMinX, qMaxX }, Occur.MUST);
413  Query qNonXDL = this.MakeXDL(false, qMinMax);
414 
415  // apply the non-XDL or XDL X conditions
416  if ((bbox.GetMinX() <= -180.0) && bbox.GetMaxX() >= 180.0)
417  {
418  xConditions = this.MakeQuery(new Query[] { qNonXDL, qXDL }, Occur.SHOULD);
419  }
420  else
421  {
422  xConditions = qNonXDL;
423  }
424 
425  // queries that cross the date line
426  }
427  else
428  {
429 
430  // X Conditions for documents that do not cross the date line
431 
432  // the document should be within the left portion of the query
433  // docMinX >= queryExtent.GetMinX() AND docMaxX <= 180.0
434  Query qMinXLeft = NumericRangeQuery.NewDoubleRange(field_minX, precisionStep, bbox.GetMinX(), null, true, false);
435  Query qMaxXLeft = NumericRangeQuery.NewDoubleRange(field_maxX, precisionStep, null, 180.0, false, true);
436  Query qLeft = this.MakeQuery(new Query[] { qMinXLeft, qMaxXLeft }, Occur.MUST);
437 
438  // the document should be within the right portion of the query
439  // docMinX >= -180.0 AND docMaxX <= queryExtent.GetMaxX()
440  Query qMinXRight = NumericRangeQuery.NewDoubleRange(field_minX, precisionStep, -180.0, null, true, false);
441  Query qMaxXRight = NumericRangeQuery.NewDoubleRange(field_maxX, precisionStep, null, bbox.GetMaxX(), false, true);
442  Query qRight = this.MakeQuery(new Query[] { qMinXRight, qMaxXRight }, Occur.MUST);
443 
444  // either left or right conditions should occur,
445  // apply the left and right conditions to documents that do not cross the date line
446  Query qLeftRight = this.MakeQuery(new Query[] { qLeft, qRight }, Occur.SHOULD);
447  Query qNonXDL = this.MakeXDL(false, qLeftRight);
448 
449  // apply the non-XDL and XDL conditions
450  xConditions = this.MakeQuery(new Query[] { qNonXDL, qXDL }, Occur.SHOULD);
451  }
452 
453  // both X and Y conditions must occur
454  return this.MakeQuery(new Query[] { xConditions, yConditions }, Occur.MUST);
455  }
456 
457  /*
458  * Constructs a query to retrieve documents that do or do not cross the date line.
459  *
460  *
461  * @param crossedDateLine <code>true</true> for documents that cross the date line
462  * @return the query
463  */
464  public Query MakeXDL(bool crossedDateLine)
465  {
466  // The 'T' and 'F' values match solr fields
467  return new TermQuery(new Term(field_xdl, crossedDateLine ? "T" : "F"));
468  }
469 
470  /*
471  * Constructs a query to retrieve documents that do or do not cross the date line
472  * and match the supplied spatial query.
473  *
474  * @param crossedDateLine <code>true</true> for documents that cross the date line
475  * @param query the spatial query
476  * @return the query
477  */
478  public Query MakeXDL(bool crossedDateLine, Query query)
479  {
480  var bq = new BooleanQuery
481  {{this.MakeXDL(crossedDateLine), Occur.MUST}, {query, Occur.MUST}};
482  return bq;
483  }
484  }
485 }