| /* |
| * 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.io.IOException; |
| import java.util.Arrays; |
| import java.util.Map; |
| import java.util.SortedMap; |
| import java.util.TreeMap; |
| |
| import org.apache.lucene.index.IndexReaderContext; |
| import org.apache.lucene.search.DoubleValuesSource; |
| import org.apache.lucene.spatial.prefix.tree.Cell; |
| import org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree; |
| import org.apache.lucene.util.Bits; |
| import org.locationtech.spatial4j.shape.Point; |
| import org.locationtech.spatial4j.shape.Shape; |
| |
| import static org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape; |
| |
| /** A PrefixTree based on Number/Date ranges. This isn't very "spatial" on the surface (to the user) but |
| * it's implemented using spatial so that's why it's here extending a SpatialStrategy. When using this class, you will |
| * use various utility methods on the prefix tree implementation to convert objects/strings to/from shapes. |
| * |
| * To use with dates, pass in {@link org.apache.lucene.spatial.prefix.tree.DateRangePrefixTree}. |
| * |
| * @lucene.experimental |
| */ |
| public class NumberRangePrefixTreeStrategy extends RecursivePrefixTreeStrategy { |
| |
| public NumberRangePrefixTreeStrategy(NumberRangePrefixTree prefixTree, String fieldName) { |
| super(prefixTree, fieldName); |
| setPruneLeafyBranches(false); |
| setPrefixGridScanLevel(prefixTree.getMaxLevels()-2);//user might want to change, however |
| setPointsOnly(false); |
| setDistErrPct(0); |
| } |
| |
| @Override |
| public NumberRangePrefixTree getGrid() { |
| return (NumberRangePrefixTree) super.getGrid(); |
| } |
| |
| @Override |
| protected boolean isPointShape(Shape shape) { |
| if (shape instanceof NumberRangePrefixTree.UnitNRShape) { |
| return ((NumberRangePrefixTree.UnitNRShape)shape).getLevel() == grid.getMaxLevels(); |
| } else { |
| return false; |
| } |
| } |
| |
| @Override |
| protected boolean isGridAlignedShape(Shape shape) { |
| // any UnitNRShape other than the world is a single cell/term |
| if (shape instanceof NumberRangePrefixTree.UnitNRShape) { |
| return ((NumberRangePrefixTree.UnitNRShape)shape).getLevel() > 0; |
| } else { |
| return false; |
| } |
| } |
| |
| /** Unsupported. */ |
| @Override |
| public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** Calculates facets between {@code start} and {@code end} to a detail level one greater than that provided by the |
| * arguments. For example providing March to October of 2014 would return facets to the day level of those months. |
| * This is just a convenience method. |
| * @see #calcFacets(IndexReaderContext, Bits, Shape, int) |
| */ |
| public Facets calcFacets(IndexReaderContext context, Bits topAcceptDocs, UnitNRShape start, UnitNRShape end) |
| throws IOException { |
| Shape facetRange = getGrid().toRangeShape(start, end); |
| int detailLevel = Math.max(start.getLevel(), end.getLevel()) + 1; |
| return calcFacets(context, topAcceptDocs, facetRange, detailLevel); |
| } |
| |
| /** |
| * Calculates facets (aggregated counts) given a range shape (start-end span) and a level, which specifies the detail. |
| * To get the level of an existing shape, say a Calendar, call |
| * {@link org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree#toUnitShape(Object)} then call |
| * {@link org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape#getLevel()}. |
| * Facet computation is implemented by navigating the underlying indexed terms efficiently. |
| */ |
| public Facets calcFacets(IndexReaderContext context, Bits topAcceptDocs, Shape facetRange, final int level) |
| throws IOException { |
| final Facets facets = new Facets(level); |
| PrefixTreeFacetCounter.compute(this, context, topAcceptDocs, facetRange, level, |
| new PrefixTreeFacetCounter.FacetVisitor() { |
| Facets.FacetParentVal parentFacet; |
| UnitNRShape parentShape; |
| |
| @Override |
| public void visit(Cell cell, int count) { |
| if (cell.getLevel() < level - 1) {//some ancestor of parent facet level, direct or distant |
| parentFacet = null;//reset |
| parentShape = null;//reset |
| facets.topLeaves += count; |
| } else if (cell.getLevel() == level - 1) {//parent |
| //set up FacetParentVal |
| setupParent((UnitNRShape) cell.getShape()); |
| parentFacet.parentLeaves += count; |
| } else {//at facet level |
| UnitNRShape unitShape = (UnitNRShape) cell.getShape(); |
| UnitNRShape unitShapeParent = unitShape.getShapeAtLevel(unitShape.getLevel() - 1); |
| if (parentFacet == null || !parentShape.equals(unitShapeParent)) { |
| setupParent(unitShapeParent); |
| } |
| //lazy init childCounts |
| if (parentFacet.childCounts == null) { |
| parentFacet.childCounts = new int[parentFacet.childCountsLen]; |
| } |
| parentFacet.childCounts[unitShape.getValAtLevel(cell.getLevel())] += count; |
| } |
| } |
| |
| private void setupParent(UnitNRShape unitShape) { |
| parentShape = unitShape.clone(); |
| //Look for existing parentFacet (from previous segment), or create anew if needed |
| parentFacet = facets.parents.get(parentShape); |
| if (parentFacet == null) {//didn't find one; make a new one |
| parentFacet = new Facets.FacetParentVal(); |
| parentFacet.childCountsLen = getGrid().getNumSubCells(parentShape); |
| facets.parents.put(parentShape, parentFacet); |
| } |
| } |
| }); |
| return facets; |
| } |
| |
| /** Facet response information */ |
| public static class Facets { |
| //TODO consider a variable-level structure -- more general purpose. |
| |
| public Facets(int detailLevel) { |
| this.detailLevel = detailLevel; |
| } |
| |
| /** The bottom-most detail-level counted, as requested. */ |
| public final int detailLevel; |
| |
| /** |
| * The count of documents with ranges that completely spanned the parents of the detail level. In more technical |
| * terms, this is the count of leaf cells 2 up and higher from the bottom. Usually you only care about counts at |
| * detailLevel, and so you will add this number to all other counts below, including to omitted/implied children |
| * counts of 0. If there are no indexed ranges (just instances, i.e. fully specified dates) then this value will |
| * always be 0. |
| */ |
| public int topLeaves; |
| |
| /** Holds all the {@link FacetParentVal} instances in order of the key. This is sparse; there won't be an |
| * instance if it's count and children are all 0. The keys are {@link org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape} shapes, which can be |
| * converted back to the original Object (i.e. a Calendar) via |
| * {@link NumberRangePrefixTree#toObject(org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape)}. */ |
| public final SortedMap<UnitNRShape,FacetParentVal> parents = new TreeMap<>(); |
| |
| /** Holds a block of detailLevel counts aggregated to their parent level. */ |
| public static class FacetParentVal { |
| |
| /** The count of ranges that span all of the childCounts. In more technical terms, this is the number of leaf |
| * cells found at this parent. Treat this like {@link Facets#topLeaves}. */ |
| public int parentLeaves; |
| |
| /** The length of {@link #childCounts}. If childCounts is not null then this is childCounts.length, otherwise it |
| * says how long it would have been if it weren't null. */ |
| public int childCountsLen; |
| |
| /** The detail level counts. It will be null if there are none, and thus they are assumed 0. Most apps, when |
| * presenting the information, will add {@link #topLeaves} and {@link #parentLeaves} to each count. */ |
| public int[] childCounts; |
| //assert childCountsLen == childCounts.length |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder buf = new StringBuilder(2048); |
| buf.append("Facets: level=").append(detailLevel).append(" topLeaves=").append(topLeaves).append(" parentCount=").append(parents.size()); |
| for (Map.Entry<UnitNRShape, FacetParentVal> entry : parents.entrySet()) { |
| buf.append('\n'); |
| if (buf.length() > 1000) { |
| buf.append("..."); |
| break; |
| } |
| final FacetParentVal pVal = entry.getValue(); |
| buf.append(' ').append(entry.getKey()).append(" leafCount=").append(pVal.parentLeaves); |
| if (pVal.childCounts != null) { |
| buf.append(' ').append(Arrays.toString(pVal.childCounts)); |
| } |
| } |
| return buf.toString(); |
| } |
| } |
| |
| } |