package org.apache.lucene.spatial.prefix.tree; /* * 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. */ import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.SpatialRelation; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; /** * Represents a grid cell. These are not necessarily thread-safe, although new * Cell("") (world cell) must be. * * @lucene.experimental */ public abstract class Cell implements Comparable<Cell> { public static final byte LEAF_BYTE = '+';//NOTE: must sort before letters & numbers /* Holds a byte[] and/or String representation of the cell. Both are lazy constructed from the other. Neither contains the trailing leaf byte. */ private byte[] bytes; private int b_off; private int b_len; private String token;//this is the only part of equality /** * When set via getSubCells(filter), it is the relationship between this cell * and the given shape filter. */ protected SpatialRelation shapeRel; /** * Always false for points. Otherwise, indicate no further sub-cells are going * to be provided because shapeRel is WITHIN or maxLevels or a detailLevel is * hit. */ protected boolean leaf; protected Cell(String token) { this.token = token; if (token.length() > 0 && token.charAt(token.length() - 1) == (char) LEAF_BYTE) { this.token = token.substring(0, token.length() - 1); setLeaf(); } if (getLevel() == 0) getShape();//ensure any lazy instantiation completes to make this threadsafe } protected Cell(byte[] bytes, int off, int len) { this.bytes = bytes; this.b_off = off; this.b_len = len; b_fixLeaf(); } public void reset(byte[] bytes, int off, int len) { assert getLevel() != 0; token = null; shapeRel = null; this.bytes = bytes; this.b_off = off; this.b_len = len; b_fixLeaf(); } private void b_fixLeaf() { //note that non-point shapes always have the maxLevels cell set with setLeaf if (bytes[b_off + b_len - 1] == LEAF_BYTE) { b_len--; setLeaf(); } else { leaf = false; } } public SpatialRelation getShapeRel() { return shapeRel; } /** * For points, this is always false. Otherwise this is true if there are no * further cells with this prefix for the shape (always true at maxLevels). */ public boolean isLeaf() { return leaf; } /** Note: not supported at level 0. */ public void setLeaf() { assert getLevel() != 0; leaf = true; } /** * Note: doesn't contain a trailing leaf byte. */ public String getTokenString() { if (token == null) { token = new String(bytes, b_off, b_len, SpatialPrefixTree.UTF8); } return token; } /** * Note: doesn't contain a trailing leaf byte. */ public byte[] getTokenBytes() { if (bytes != null) { if (b_off != 0 || b_len != bytes.length) { throw new IllegalStateException("Not supported if byte[] needs to be recreated."); } } else { bytes = token.getBytes(SpatialPrefixTree.UTF8); b_off = 0; b_len = bytes.length; } return bytes; } public int getLevel() { return token != null ? token.length() : b_len; } //TODO add getParent() and update some algorithms to use this? //public Cell getParent(); /** * Like {@link #getSubCells()} but with the results filtered by a shape. If * that shape is a {@link com.spatial4j.core.shape.Point} then it must call * {@link #getSubCell(com.spatial4j.core.shape.Point)}. The returned cells * should have {@link Cell#getShapeRel()} set to their relation with {@code * shapeFilter}. In addition, {@link Cell#isLeaf()} * must be true when that relation is WITHIN. * <p/> * Precondition: Never called when getLevel() == maxLevel. * * @param shapeFilter an optional filter for the returned cells. * @return A set of cells (no dups), sorted. Not Modifiable. */ public Collection<Cell> getSubCells(Shape shapeFilter) { //Note: Higher-performing subclasses might override to consider the shape filter to generate fewer cells. if (shapeFilter instanceof Point) { Cell subCell = getSubCell((Point) shapeFilter); subCell.shapeRel = SpatialRelation.CONTAINS; return Collections.singletonList(subCell); } Collection<Cell> cells = getSubCells(); if (shapeFilter == null) { return cells; } //TODO change API to return a filtering iterator List<Cell> copy = new ArrayList<>(cells.size()); for (Cell cell : cells) { SpatialRelation rel = cell.getShape().relate(shapeFilter); if (rel == SpatialRelation.DISJOINT) continue; cell.shapeRel = rel; if (rel == SpatialRelation.WITHIN) cell.setLeaf(); copy.add(cell); } return copy; } /** * Performant implementations are expected to implement this efficiently by * considering the current cell's boundary. Precondition: Never called when * getLevel() == maxLevel. * <p/> * Precondition: this.getShape().relate(p) != DISJOINT. */ public abstract Cell getSubCell(Point p); //TODO Cell getSubCell(byte b) /** * Gets the cells at the next grid cell level that cover this cell. * Precondition: Never called when getLevel() == maxLevel. * * @return A set of cells (no dups), sorted, modifiable, not empty, not null. */ protected abstract Collection<Cell> getSubCells(); /** * {@link #getSubCells()}.size() -- usually a constant. Should be >=2 */ public abstract int getSubCellsSize(); public abstract Shape getShape(); public Point getCenter() { return getShape().getCenter(); } @Override public int compareTo(Cell o) { return getTokenString().compareTo(o.getTokenString()); } @Override public boolean equals(Object obj) { return !(obj == null || !(obj instanceof Cell)) && getTokenString().equals(((Cell) obj).getTokenString()); } @Override public int hashCode() { return getTokenString().hashCode(); } @Override public String toString() { return getTokenString() + (isLeaf() ? (char) LEAF_BYTE : ""); } }