/* * 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. */ package org.apache.lucene.spatial.composite; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Shape; import org.apache.lucene.document.Field; import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.search.Query; import org.apache.lucene.spatial.SpatialStrategy; import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy; import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree; import org.apache.lucene.spatial.query.SpatialArgs; import org.apache.lucene.spatial.query.SpatialOperation; import org.apache.lucene.spatial.query.UnsupportedSpatialOperation; import org.apache.lucene.spatial.serialized.SerializedDVStrategy; import org.apache.lucene.spatial.util.ShapePredicateValueSource; /** * A composite {@link SpatialStrategy} based on {@link RecursivePrefixTreeStrategy} (RPT) and * {@link SerializedDVStrategy} (SDV). * RPT acts as an index to the precision available in SDV, and in some circumstances can avoid geometry lookups based * on where a cell is in relation to the query shape. Currently the only predicate optimized like this is Intersects. * All predicates are supported except for the BBox* ones, and Disjoint. * * @lucene.experimental */ public class CompositeSpatialStrategy extends SpatialStrategy { //TODO support others? (BBox) private final RecursivePrefixTreeStrategy indexStrategy; /** Has the geometry. */ // TODO support others? private final SerializedDVStrategy geometryStrategy; private boolean optimizePredicates = true; public CompositeSpatialStrategy(String fieldName, RecursivePrefixTreeStrategy indexStrategy, SerializedDVStrategy geometryStrategy) { super(indexStrategy.getSpatialContext(), fieldName);//field name; unused this.indexStrategy = indexStrategy; this.geometryStrategy = geometryStrategy; } public RecursivePrefixTreeStrategy getIndexStrategy() { return indexStrategy; } public SerializedDVStrategy getGeometryStrategy() { return geometryStrategy; } public boolean isOptimizePredicates() { return optimizePredicates; } /** Set to false to NOT use optimized search predicates that avoid checking the geometry sometimes. Only useful for * benchmarking. */ public void setOptimizePredicates(boolean optimizePredicates) { this.optimizePredicates = optimizePredicates; } @Override public Field[] createIndexableFields(Shape shape) { List<Field> fields = new ArrayList<>(); Collections.addAll(fields, indexStrategy.createIndexableFields(shape)); Collections.addAll(fields, geometryStrategy.createIndexableFields(shape)); return fields.toArray(new Field[fields.size()]); } @Override public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) { //TODO consider indexing center-point in DV? Guarantee contained by the shape, which could then be used for // other purposes like faster WITHIN predicate? throw new UnsupportedOperationException(); } @Override public Query makeQuery(SpatialArgs args) { final SpatialOperation pred = args.getOperation(); if (pred == SpatialOperation.BBoxIntersects || pred == SpatialOperation.BBoxWithin) { throw new UnsupportedSpatialOperation(pred); } if (pred == SpatialOperation.IsDisjointTo) { // final Query intersectQuery = makeQuery(new SpatialArgs(SpatialOperation.Intersects, args.getShape())); // DocValues.getDocsWithField(reader, geometryStrategy.getFieldName()); //TODO resurrect Disjoint spatial query utility accepting a field name known to have DocValues. // update class docs when it's added. throw new UnsupportedSpatialOperation(pred); } final ShapePredicateValueSource predicateValueSource = new ShapePredicateValueSource(geometryStrategy.makeShapeValueSource(), pred, args.getShape()); //System.out.println("PredOpt: " + optimizePredicates); if (pred == SpatialOperation.Intersects && optimizePredicates) { // We have a smart Intersects impl final SpatialPrefixTree grid = indexStrategy.getGrid(); final int detailLevel = grid.getLevelForDistance(args.resolveDistErr(ctx, 0.0));//default to max precision return new IntersectsRPTVerifyQuery(args.getShape(), indexStrategy.getFieldName(), grid, detailLevel, indexStrategy.getPrefixGridScanLevel(), predicateValueSource); } else { //The general path; all index matches get verified SpatialArgs indexArgs; if (pred == SpatialOperation.Contains) { // note: we could map IsWithin as well but it's pretty darned slow since it touches all world grids indexArgs = args; } else { //TODO add args.clone method with new predicate? Or simply make non-final? indexArgs = new SpatialArgs(SpatialOperation.Intersects, args.getShape()); indexArgs.setDistErr(args.getDistErr()); indexArgs.setDistErrPct(args.getDistErrPct()); } if (indexArgs.getDistErr() == null && indexArgs.getDistErrPct() == null) { indexArgs.setDistErrPct(0.10); } final Query indexQuery = indexStrategy.makeQuery(indexArgs); return new CompositeVerifyQuery(indexQuery, predicateValueSource); } } }