| /* |
| * 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.prefix; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.lucene.search.Query; |
| import org.apache.lucene.search.TermInSetQuery; |
| import org.apache.lucene.spatial.prefix.tree.Cell; |
| import org.apache.lucene.spatial.prefix.tree.CellIterator; |
| 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.util.BytesRef; |
| import org.apache.lucene.util.BytesRefBuilder; |
| import org.locationtech.spatial4j.shape.Point; |
| import org.locationtech.spatial4j.shape.Shape; |
| |
| /** |
| * A basic implementation of {@link PrefixTreeStrategy} using a large |
| * {@link TermInSetQuery} of all the cells from |
| * {@link SpatialPrefixTree#getTreeCellIterator(org.locationtech.spatial4j.shape.Shape, int)}. |
| * It only supports the search of indexed Point shapes. |
| * <p> |
| * The precision of query shapes (distErrPct) is an important factor in using |
| * this Strategy. If the precision is too precise then it will result in many |
| * terms which will amount to a slower query. |
| * |
| * @lucene.experimental |
| */ |
| public class TermQueryPrefixTreeStrategy extends PrefixTreeStrategy { |
| |
| protected boolean simplifyIndexedCells = false; |
| |
| public TermQueryPrefixTreeStrategy(SpatialPrefixTree grid, String fieldName) { |
| super(grid, fieldName); |
| } |
| |
| @Override |
| protected CellToBytesRefIterator newCellToBytesRefIterator() { |
| //Ensure we don't have leaves, as this strategy doesn't handle them. |
| return new CellToBytesRefIterator() { |
| @Override |
| public BytesRef next() { |
| if (!cellIter.hasNext()) { |
| return null; |
| } |
| return cellIter.next().getTokenBytesNoLeaf(bytesRef); |
| } |
| }; |
| } |
| |
| @Override |
| public Query makeQuery(SpatialArgs args) { |
| final SpatialOperation op = args.getOperation(); |
| if (op != SpatialOperation.Intersects) |
| throw new UnsupportedSpatialOperation(op); |
| |
| Shape shape = args.getShape(); |
| int detailLevel = grid.getLevelForDistance(args.resolveDistErr(ctx, distErrPct)); |
| |
| //--get a List of BytesRef for each term we want (no parents, no leaf bytes)) |
| final int GUESS_NUM_TERMS; |
| if (shape instanceof Point) |
| GUESS_NUM_TERMS = detailLevel;//perfect guess |
| else |
| GUESS_NUM_TERMS = 4096;//should this be a method on SpatialPrefixTree? |
| |
| BytesRefBuilder masterBytes = new BytesRefBuilder();//shared byte array for all terms |
| List<BytesRef> terms = new ArrayList<>(GUESS_NUM_TERMS); |
| |
| CellIterator cells = grid.getTreeCellIterator(shape, detailLevel); |
| while (cells.hasNext()) { |
| Cell cell = cells.next(); |
| if (!cell.isLeaf()) |
| continue; |
| BytesRef term = cell.getTokenBytesNoLeaf(null);//null because we want a new BytesRef |
| //We copy out the bytes because it may be re-used across the iteration. This also gives us the opportunity |
| // to use one contiguous block of memory for the bytes of all terms we need. |
| masterBytes.grow(masterBytes.length() + term.length); |
| masterBytes.append(term); |
| term.bytes = null;//don't need; will reset later |
| term.offset = masterBytes.length() - term.length; |
| terms.add(term); |
| } |
| //doing this now because if we did earlier, it's possible the bytes needed to grow() |
| for (BytesRef byteRef : terms) { |
| byteRef.bytes = masterBytes.bytes(); |
| } |
| //unfortunately TermsQuery will needlessly sort & dedupe |
| //TODO an automatonQuery might be faster? |
| return new TermInSetQuery(getFieldName(), terms); |
| } |
| |
| } |