/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using Lucene.Net.Documents; using Lucene.Net.Search; using Lucene.Net.Search.Function; using Lucene.Net.Spatial.Queries; using Lucene.Net.Spatial.Util; using Spatial4n.Core.Context; using Spatial4n.Core.Shapes; namespace Lucene.Net.Spatial.Vector { /// /// Simple {@link SpatialStrategy} which represents Points in two numeric {@link DoubleField}s. /// /// Note, currently only Points can be indexed by this Strategy. At query time, the bounding /// box of the given Shape is used to create {@link NumericRangeQuery}s to efficiently /// find Points within the Shape. /// /// Due to the simple use of numeric fields, this Strategy provides support for sorting by /// distance through {@link DistanceValueSource} /// public class PointVectorStrategy : SpatialStrategy { public static String SUFFIX_X = "__x"; public static String SUFFIX_Y = "__y"; private readonly String fieldNameX; private readonly String fieldNameY; public int precisionStep = 8; // same as solr default public PointVectorStrategy(SpatialContext ctx, String fieldNamePrefix) : base(ctx, fieldNamePrefix) { this.fieldNameX = fieldNamePrefix + SUFFIX_X; this.fieldNameY = fieldNamePrefix + SUFFIX_Y; } public void SetPrecisionStep(int p) { precisionStep = p; if (precisionStep <= 0 || precisionStep >= 64) precisionStep = int.MaxValue; } public string GetFieldNameX() { return fieldNameX; } public string GetFieldNameY() { return fieldNameY; } public override AbstractField[] CreateIndexableFields(Shape shape) { var point = shape as Point; if (point != null) return CreateIndexableFields(point); throw new InvalidOperationException("Can only index Point, not " + shape); } public AbstractField[] CreateIndexableFields(Point point) { var f = new AbstractField[2]; var f0 = new NumericField(fieldNameX, precisionStep, Field.Store.NO, true) {OmitNorms = true, OmitTermFreqAndPositions = true}; f0.SetDoubleValue(point.GetX()); f[0] = f0; var f1 = new NumericField(fieldNameY, precisionStep, Field.Store.NO, true) {OmitNorms = true, OmitTermFreqAndPositions = true}; f1.SetDoubleValue(point.GetY()); f[1] = f1; return f; } public override ValueSource MakeDistanceValueSource(Point queryPoint) { return new DistanceValueSource(this, queryPoint); } public override ConstantScoreQuery MakeQuery(SpatialArgs args) { if (!SpatialOperation.Is(args.Operation, SpatialOperation.Intersects, SpatialOperation.IsWithin)) throw new UnsupportedSpatialOperation(args.Operation); Shape shape = args.Shape; var bbox = shape as Rectangle; if (bbox != null) return new ConstantScoreQuery(new QueryWrapperFilter(MakeWithin(bbox))); var circle = shape as Circle; if (circle != null) { bbox = circle.GetBoundingBox(); var vsf = new ValueSourceFilter( new QueryWrapperFilter(MakeWithin(bbox)), MakeDistanceValueSource(circle.GetCenter()), 0, circle.GetRadius()); return new ConstantScoreQuery(vsf); } throw new InvalidOperationException("Only Rectangles and Circles are currently supported, " + "found [" + shape.GetType().Name + "]"); //TODO } //TODO this is basically old code that hasn't been verified well and should probably be removed public Query MakeQueryDistanceScore(SpatialArgs args) { // For starters, just limit the bbox var shape = args.Shape; if (!(shape is Rectangle || shape is Circle)) throw new InvalidOperationException("Only Rectangles and Circles are currently supported, found [" + shape.GetType().Name + "]");//TODO Rectangle bbox = shape.GetBoundingBox(); if (bbox.GetCrossesDateLine()) { throw new InvalidOperationException("Crossing dateline not yet supported"); } ValueSource valueSource = null; Query spatial = null; SpatialOperation op = args.Operation; if (SpatialOperation.Is(op, SpatialOperation.BBoxWithin, SpatialOperation.BBoxIntersects)) { spatial = MakeWithin(bbox); } else if (SpatialOperation.Is(op, SpatialOperation.Intersects, SpatialOperation.IsWithin)) { spatial = MakeWithin(bbox); var circle = args.Shape as Circle; if (circle != null) { // Make the ValueSource valueSource = MakeDistanceValueSource(shape.GetCenter()); var vsf = new ValueSourceFilter( new QueryWrapperFilter(spatial), valueSource, 0, circle.GetRadius()); spatial = new FilteredQuery(new MatchAllDocsQuery(), vsf); } } else if (op == SpatialOperation.IsDisjointTo) { spatial = MakeDisjoint(bbox); } if (spatial == null) { throw new UnsupportedSpatialOperation(args.Operation); } if (valueSource != null) { valueSource = new CachingDoubleValueSource(valueSource); } else { valueSource = MakeDistanceValueSource(shape.GetCenter()); } Query spatialRankingQuery = new FunctionQuery(valueSource); var bq = new BooleanQuery(); bq.Add(spatial, Occur.MUST); bq.Add(spatialRankingQuery, Occur.MUST); return bq; } public override Filter MakeFilter(SpatialArgs args) { //unwrap the CSQ from makeQuery ConstantScoreQuery csq = MakeQuery(args); Filter filter = csq.Filter; if (filter != null) return filter; else return new QueryWrapperFilter(csq); } /// /// Constructs a query to retrieve documents that fully contain the input envelope. /// /// private Query MakeWithin(Rectangle bbox) { var bq = new BooleanQuery(); const Occur MUST = Occur.MUST; if (bbox.GetCrossesDateLine()) { //use null as performance trick since no data will be beyond the world bounds bq.Add(RangeQuery(fieldNameX, null /*-180*/, bbox.GetMaxX()), Occur.SHOULD); bq.Add(RangeQuery(fieldNameX, bbox.GetMinX(), null /*+180*/), Occur.SHOULD); bq.MinimumNumberShouldMatch = 1; //must match at least one of the SHOULD } else { bq.Add(RangeQuery(fieldNameX, bbox.GetMinX(), bbox.GetMaxX()), MUST); } bq.Add(RangeQuery(fieldNameY, bbox.GetMinY(), bbox.GetMaxY()), MUST); return bq; } private NumericRangeQuery RangeQuery(String fieldName, double? min, double? max) { return NumericRangeQuery.NewDoubleRange( fieldName, precisionStep, min, max, true, true); //inclusive } /// /// Constructs a query to retrieve documents that fully contain the input envelope. /// /// private Query MakeDisjoint(Rectangle bbox) { if (bbox.GetCrossesDateLine()) throw new InvalidOperationException("MakeDisjoint doesn't handle dateline cross"); Query qX = RangeQuery(fieldNameX, bbox.GetMinX(), bbox.GetMaxX()); Query qY = RangeQuery(fieldNameY, bbox.GetMinY(), bbox.GetMaxY()); var bq = new BooleanQuery {{qX, Occur.MUST_NOT}, {qY, Occur.MUST_NOT}}; return bq; } } }