/*
* 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 DateRangePrefixTree
. @lucene.experimental
/** 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. /** Unsupported. */
@Override
public DoubleValuesSource makeDistanceValueSource(Point queryPoint, double multiplier) {
throw new UnsupportedOperationException();
}
Calculates facets between start
and 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 Also:
/** 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 NumberRangePrefixTree.toUnitShape(Object)
then call UnitNRShape.getLevel()
. Facet computation is implemented by navigating the underlying indexed terms efficiently. /**
* 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 /** 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. /** 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.
/**
* 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 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 UnitNRShape
shapes, which can be converted back to the original Object (i.e. a Calendar) via NumberRangePrefixTree.toObject(UnitNRShape)
. /** 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. /** 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 Facets.topLeaves
. /** 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 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. /** 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 Facets.topLeaves
and parentLeaves
to each count. /** 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();
}
}
}