mirror of https://github.com/apache/lucene.git
LUCENE-3795: moving prefix/tree stuff into strategy
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/branches/lucene3795_lsp_spatial_module@1295444 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
4fd44f3d41
commit
fdb0bd695e
|
@ -1,2 +1,2 @@
|
||||||
AnyObjectId[41698d61caf8eee1afdca7d39042a2eee1517ff5] was removed in git history.
|
AnyObjectId[6d3c8ca133496a20135a1d0fdb7f77811794f899] was removed in git history.
|
||||||
Apache SVN contains full history.
|
Apache SVN contains full history.
|
|
@ -17,9 +17,10 @@
|
||||||
|
|
||||||
package org.apache.lucene.spatial.strategy.prefix;
|
package org.apache.lucene.spatial.strategy.prefix;
|
||||||
|
|
||||||
import com.spatial4j.core.prefix.Node;
|
|
||||||
import com.spatial4j.core.prefix.SpatialPrefixTree;
|
|
||||||
import com.spatial4j.core.shape.Point;
|
import com.spatial4j.core.shape.Point;
|
||||||
|
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.Node;
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.SpatialPrefixTree;
|
||||||
import org.apache.lucene.spatial.strategy.util.ShapeFieldCacheProvider;
|
import org.apache.lucene.spatial.strategy.util.ShapeFieldCacheProvider;
|
||||||
import org.apache.lucene.util.BytesRef;
|
import org.apache.lucene.util.BytesRef;
|
||||||
|
|
||||||
|
|
|
@ -25,13 +25,13 @@ import org.apache.lucene.document.StoredField;
|
||||||
import org.apache.lucene.index.IndexableField;
|
import org.apache.lucene.index.IndexableField;
|
||||||
import org.apache.lucene.queries.function.ValueSource;
|
import org.apache.lucene.queries.function.ValueSource;
|
||||||
import com.spatial4j.core.distance.DistanceCalculator;
|
import com.spatial4j.core.distance.DistanceCalculator;
|
||||||
import com.spatial4j.core.prefix.Node;
|
|
||||||
import com.spatial4j.core.prefix.SpatialPrefixTree;
|
|
||||||
import com.spatial4j.core.query.SpatialArgs;
|
import com.spatial4j.core.query.SpatialArgs;
|
||||||
import com.spatial4j.core.shape.Point;
|
import com.spatial4j.core.shape.Point;
|
||||||
import com.spatial4j.core.shape.Shape;
|
import com.spatial4j.core.shape.Shape;
|
||||||
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
||||||
import org.apache.lucene.spatial.strategy.SpatialStrategy;
|
import org.apache.lucene.spatial.strategy.SpatialStrategy;
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.Node;
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.SpatialPrefixTree;
|
||||||
import org.apache.lucene.spatial.strategy.util.CachedDistanceValueSource;
|
import org.apache.lucene.spatial.strategy.util.CachedDistanceValueSource;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
|
@ -21,9 +21,10 @@ import org.apache.lucene.index.*;
|
||||||
import org.apache.lucene.search.DocIdSet;
|
import org.apache.lucene.search.DocIdSet;
|
||||||
import org.apache.lucene.search.DocIdSetIterator;
|
import org.apache.lucene.search.DocIdSetIterator;
|
||||||
import org.apache.lucene.search.Filter;
|
import org.apache.lucene.search.Filter;
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.Node;
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.SpatialPrefixTree;
|
||||||
|
|
||||||
import com.spatial4j.core.shape.SpatialRelation;
|
import com.spatial4j.core.shape.SpatialRelation;
|
||||||
import com.spatial4j.core.prefix.Node;
|
|
||||||
import com.spatial4j.core.prefix.SpatialPrefixTree;
|
|
||||||
import com.spatial4j.core.shape.Shape;
|
import com.spatial4j.core.shape.Shape;
|
||||||
import org.apache.lucene.util.Bits;
|
import org.apache.lucene.util.Bits;
|
||||||
import org.apache.lucene.util.BytesRef;
|
import org.apache.lucene.util.BytesRef;
|
||||||
|
|
|
@ -23,11 +23,11 @@ import org.apache.lucene.search.Query;
|
||||||
import org.apache.lucene.queries.function.ValueSource;
|
import org.apache.lucene.queries.function.ValueSource;
|
||||||
import org.apache.lucene.queries.function.FunctionQuery;
|
import org.apache.lucene.queries.function.FunctionQuery;
|
||||||
import com.spatial4j.core.exception.UnsupportedSpatialOperation;
|
import com.spatial4j.core.exception.UnsupportedSpatialOperation;
|
||||||
import com.spatial4j.core.prefix.SpatialPrefixTree;
|
|
||||||
import com.spatial4j.core.query.SpatialArgs;
|
import com.spatial4j.core.query.SpatialArgs;
|
||||||
import com.spatial4j.core.query.SpatialOperation;
|
import com.spatial4j.core.query.SpatialOperation;
|
||||||
import com.spatial4j.core.shape.Shape;
|
import com.spatial4j.core.shape.Shape;
|
||||||
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.SpatialPrefixTree;
|
||||||
|
|
||||||
|
|
||||||
public class RecursivePrefixTreeStrategy extends PrefixTreeStrategy {
|
public class RecursivePrefixTreeStrategy extends PrefixTreeStrategy {
|
||||||
|
|
|
@ -20,12 +20,12 @@ package org.apache.lucene.spatial.strategy.prefix;
|
||||||
import org.apache.lucene.index.Term;
|
import org.apache.lucene.index.Term;
|
||||||
import org.apache.lucene.search.*;
|
import org.apache.lucene.search.*;
|
||||||
import com.spatial4j.core.exception.UnsupportedSpatialOperation;
|
import com.spatial4j.core.exception.UnsupportedSpatialOperation;
|
||||||
import com.spatial4j.core.prefix.Node;
|
|
||||||
import com.spatial4j.core.prefix.SpatialPrefixTree;
|
|
||||||
import com.spatial4j.core.query.SpatialArgs;
|
import com.spatial4j.core.query.SpatialArgs;
|
||||||
import com.spatial4j.core.query.SpatialOperation;
|
import com.spatial4j.core.query.SpatialOperation;
|
||||||
import com.spatial4j.core.shape.Shape;
|
import com.spatial4j.core.shape.Shape;
|
||||||
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.Node;
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.SpatialPrefixTree;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
* 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.strategy.prefix.tree;
|
||||||
|
|
||||||
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
|
import com.spatial4j.core.shape.Point;
|
||||||
|
import com.spatial4j.core.shape.Rectangle;
|
||||||
|
import com.spatial4j.core.shape.Shape;
|
||||||
|
import com.spatial4j.core.util.GeohashUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SpatialPrefixGrid based on Geohashes. Uses {@link GeohashUtils} to do all the geohash work.
|
||||||
|
*/
|
||||||
|
public class GeohashPrefixTree extends SpatialPrefixTree {
|
||||||
|
|
||||||
|
public static class Factory extends SpatialPrefixTreeFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getLevelForDistance(double degrees) {
|
||||||
|
GeohashPrefixTree grid = new GeohashPrefixTree(ctx, GeohashPrefixTree.getMaxLevelsPossible());
|
||||||
|
return grid.getLevelForDistance(degrees) + 1;//returns 1 greater
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SpatialPrefixTree newSPT() {
|
||||||
|
return new GeohashPrefixTree(ctx,
|
||||||
|
maxLevels != null ? maxLevels : GeohashPrefixTree.getMaxLevelsPossible());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeohashPrefixTree(SpatialContext ctx, int maxLevels) {
|
||||||
|
super(ctx, maxLevels);
|
||||||
|
Rectangle bounds = ctx.getWorldBounds();
|
||||||
|
if (bounds.getMinX() != -180)
|
||||||
|
throw new IllegalArgumentException("Geohash only supports lat-lon world bounds. Got "+bounds);
|
||||||
|
int MAXP = getMaxLevelsPossible();
|
||||||
|
if (maxLevels <= 0 || maxLevels > MAXP)
|
||||||
|
throw new IllegalArgumentException("maxLen must be [1-"+MAXP+"] but got "+ maxLevels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Any more than this and there's no point (double lat & lon are the same). */
|
||||||
|
public static int getMaxLevelsPossible() {
|
||||||
|
return GeohashUtils.MAX_PRECISION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLevelForDistance(double dist) {
|
||||||
|
final int level = GeohashUtils.lookupHashLenForWidthHeight(dist, dist);
|
||||||
|
return Math.max(Math.min(level, maxLevels), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode(Point p, int level) {
|
||||||
|
return new GhCell(GeohashUtils.encodeLatLon(p.getY(), p.getX(), level));//args are lat,lon (y,x)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode(String token) {
|
||||||
|
return new GhCell(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode(byte[] bytes, int offset, int len) {
|
||||||
|
return new GhCell(bytes, offset, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Node> getNodes(Shape shape, int detailLevel, boolean inclParents) {
|
||||||
|
return shape instanceof Point ? super.getNodesAltPoint((Point) shape, detailLevel, inclParents) :
|
||||||
|
super.getNodes(shape, detailLevel, inclParents);
|
||||||
|
}
|
||||||
|
|
||||||
|
class GhCell extends Node {
|
||||||
|
GhCell(String token) {
|
||||||
|
super(GeohashPrefixTree.this, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
GhCell(byte[] bytes, int off, int len) {
|
||||||
|
super(GeohashPrefixTree.this, bytes, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset(byte[] bytes, int off, int len) {
|
||||||
|
super.reset(bytes, off, len);
|
||||||
|
shape = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Node> getSubCells() {
|
||||||
|
String[] hashes = GeohashUtils.getSubGeohashes(getGeohash());//sorted
|
||||||
|
List<Node> cells = new ArrayList<Node>(hashes.length);
|
||||||
|
for (String hash : hashes) {
|
||||||
|
cells.add(new GhCell(hash));
|
||||||
|
}
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSubCellsSize() {
|
||||||
|
return 32;//8x4
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getSubCell(Point p) {
|
||||||
|
return GeohashPrefixTree.this.getNode(p,getLevel()+1);//not performant!
|
||||||
|
}
|
||||||
|
|
||||||
|
private Shape shape;//cache
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Shape getShape() {
|
||||||
|
if (shape == null) {
|
||||||
|
shape = GeohashUtils.decodeBoundary(getGeohash(), ctx);
|
||||||
|
}
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Point getCenter() {
|
||||||
|
return GeohashUtils.decode(getGeohash(), ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getGeohash() {
|
||||||
|
return getTokenString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}//class GhCell
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
/*
|
||||||
|
* 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.strategy.prefix.tree;
|
||||||
|
|
||||||
|
import com.spatial4j.core.shape.SpatialRelation;
|
||||||
|
import com.spatial4j.core.shape.Point;
|
||||||
|
import com.spatial4j.core.shape.Shape;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a grid cell. These are not necessarily threadsafe, although new Cell("") (world cell) must be.
|
||||||
|
*/
|
||||||
|
public abstract class Node implements Comparable<Node> {
|
||||||
|
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
|
||||||
|
|
||||||
|
protected SpatialRelation shapeRel;//set in getSubCells(filter), and via setLeaf().
|
||||||
|
private SpatialPrefixTree spatialPrefixTree;
|
||||||
|
|
||||||
|
protected Node(SpatialPrefixTree spatialPrefixTree, String token) {
|
||||||
|
this.spatialPrefixTree = spatialPrefixTree;
|
||||||
|
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 Node(SpatialPrefixTree spatialPrefixTree, byte[] bytes, int off, int len) {
|
||||||
|
this.spatialPrefixTree = spatialPrefixTree;
|
||||||
|
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() {
|
||||||
|
if (bytes[b_off + b_len - 1] == LEAF_BYTE) {
|
||||||
|
b_len--;
|
||||||
|
setLeaf();
|
||||||
|
} else if (getLevel() == spatialPrefixTree.getMaxLevels()) {
|
||||||
|
setLeaf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpatialRelation getShapeRel() {
|
||||||
|
return shapeRel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLeaf() {
|
||||||
|
return shapeRel == SpatialRelation.WITHIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLeaf() {
|
||||||
|
assert getLevel() != 0;
|
||||||
|
shapeRel = SpatialRelation.WITHIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)};
|
||||||
|
* 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<Node> getSubCells(Shape shapeFilter) {
|
||||||
|
//Note: Higher-performing subclasses might override to consider the shape filter to generate fewer cells.
|
||||||
|
if (shapeFilter instanceof Point) {
|
||||||
|
return Collections.singleton(getSubCell((Point) shapeFilter));
|
||||||
|
}
|
||||||
|
Collection<Node> cells = getSubCells();
|
||||||
|
|
||||||
|
if (shapeFilter == null) {
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
List<Node> copy = new ArrayList<Node>(cells.size());//copy since cells contractually isn't modifiable
|
||||||
|
for (Node cell : cells) {
|
||||||
|
SpatialRelation rel = cell.getShape().relate(shapeFilter, spatialPrefixTree.ctx);
|
||||||
|
if (rel == SpatialRelation.DISJOINT)
|
||||||
|
continue;
|
||||||
|
cell.shapeRel = rel;
|
||||||
|
copy.add(cell);
|
||||||
|
}
|
||||||
|
cells = copy;
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performant implementations are expected to implement this efficiently by considering the current
|
||||||
|
* cell's boundary.
|
||||||
|
* Precondition: Never called when getLevel() == maxLevel.
|
||||||
|
* Precondition: this.getShape().relate(p) != DISJOINT.
|
||||||
|
*/
|
||||||
|
public abstract Node 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. Not Modifiable.
|
||||||
|
*/
|
||||||
|
protected abstract Collection<Node> 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(Node o) {
|
||||||
|
return getTokenString().compareTo(o.getTokenString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return !(obj == null || !(obj instanceof Node)) && getTokenString().equals(((Node) obj).getTokenString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getTokenString().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getTokenString() + (isLeaf() ? (char) LEAF_BYTE : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,299 @@
|
||||||
|
/*
|
||||||
|
* 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.strategy.prefix.tree;
|
||||||
|
|
||||||
|
import com.spatial4j.core.shape.SpatialRelation;
|
||||||
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
|
import com.spatial4j.core.shape.Point;
|
||||||
|
import com.spatial4j.core.shape.Rectangle;
|
||||||
|
import com.spatial4j.core.shape.Shape;
|
||||||
|
import com.spatial4j.core.shape.simple.PointImpl;
|
||||||
|
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class QuadPrefixTree extends SpatialPrefixTree {
|
||||||
|
|
||||||
|
public static class Factory extends SpatialPrefixTreeFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getLevelForDistance(double degrees) {
|
||||||
|
QuadPrefixTree grid = new QuadPrefixTree(ctx, MAX_LEVELS_POSSIBLE);
|
||||||
|
return grid.getLevelForDistance(degrees) + 1;//returns 1 greater
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SpatialPrefixTree newSPT() {
|
||||||
|
return new QuadPrefixTree(ctx,
|
||||||
|
maxLevels != null ? maxLevels : MAX_LEVELS_POSSIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final int MAX_LEVELS_POSSIBLE = 50;//not really sure how big this should be
|
||||||
|
|
||||||
|
public static final int DEFAULT_MAX_LEVELS = 12;
|
||||||
|
private final double xmin;
|
||||||
|
private final double xmax;
|
||||||
|
private final double ymin;
|
||||||
|
private final double ymax;
|
||||||
|
private final double xmid;
|
||||||
|
private final double ymid;
|
||||||
|
|
||||||
|
private final double gridW;
|
||||||
|
public final double gridH;
|
||||||
|
|
||||||
|
final double[] levelW;
|
||||||
|
final double[] levelH;
|
||||||
|
final int[] levelS; // side
|
||||||
|
final int[] levelN; // number
|
||||||
|
|
||||||
|
public QuadPrefixTree(
|
||||||
|
SpatialContext ctx, Rectangle bounds, int maxLevels) {
|
||||||
|
super(ctx, maxLevels);
|
||||||
|
this.xmin = bounds.getMinX();
|
||||||
|
this.xmax = bounds.getMaxX();
|
||||||
|
this.ymin = bounds.getMinY();
|
||||||
|
this.ymax = bounds.getMaxY();
|
||||||
|
|
||||||
|
levelW = new double[maxLevels];
|
||||||
|
levelH = new double[maxLevels];
|
||||||
|
levelS = new int[maxLevels];
|
||||||
|
levelN = new int[maxLevels];
|
||||||
|
|
||||||
|
gridW = xmax - xmin;
|
||||||
|
gridH = ymax - ymin;
|
||||||
|
this.xmid = xmin + gridW/2.0;
|
||||||
|
this.ymid = ymin + gridH/2.0;
|
||||||
|
levelW[0] = gridW/2.0;
|
||||||
|
levelH[0] = gridH/2.0;
|
||||||
|
levelS[0] = 2;
|
||||||
|
levelN[0] = 4;
|
||||||
|
|
||||||
|
for (int i = 1; i < levelW.length; i++) {
|
||||||
|
levelW[i] = levelW[i - 1] / 2.0;
|
||||||
|
levelH[i] = levelH[i - 1] / 2.0;
|
||||||
|
levelS[i] = levelS[i - 1] * 2;
|
||||||
|
levelN[i] = levelN[i - 1] * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public QuadPrefixTree(SpatialContext ctx) {
|
||||||
|
this(ctx, DEFAULT_MAX_LEVELS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public QuadPrefixTree(
|
||||||
|
SpatialContext ctx, int maxLevels) {
|
||||||
|
this(ctx, ctx.getWorldBounds(), maxLevels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void printInfo() {
|
||||||
|
NumberFormat nf = NumberFormat.getNumberInstance();
|
||||||
|
nf.setMaximumFractionDigits(5);
|
||||||
|
nf.setMinimumFractionDigits(5);
|
||||||
|
nf.setMinimumIntegerDigits(3);
|
||||||
|
|
||||||
|
for (int i = 0; i < maxLevels; i++) {
|
||||||
|
System.out.println(i + "]\t" + nf.format(levelW[i]) + "\t" + nf.format(levelH[i]) + "\t" +
|
||||||
|
levelS[i] + "\t" + (levelS[i] * levelS[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLevelForDistance(double dist) {
|
||||||
|
for (int i = 1; i < maxLevels; i++) {
|
||||||
|
//note: level[i] is actually a lookup for level i+1
|
||||||
|
if(dist > levelW[i] || dist > levelH[i]) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxLevels;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode(Point p, int level) {
|
||||||
|
List<Node> cells = new ArrayList<Node>(1);
|
||||||
|
build(xmid, ymid, 0, cells, new StringBuilder(), new PointImpl(p.getX(),p.getY()), level);
|
||||||
|
return cells.get(0);//note cells could be longer if p on edge
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode(String token) {
|
||||||
|
return new QuadCell(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode(byte[] bytes, int offset, int len) {
|
||||||
|
return new QuadCell(bytes, offset, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override //for performance
|
||||||
|
public List<Node> getNodes(Shape shape, int detailLevel, boolean inclParents) {
|
||||||
|
if (shape instanceof Point)
|
||||||
|
return super.getNodesAltPoint((Point) shape, detailLevel, inclParents);
|
||||||
|
else
|
||||||
|
return super.getNodes(shape, detailLevel, inclParents);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void build(
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
int level,
|
||||||
|
List<Node> matches,
|
||||||
|
StringBuilder str,
|
||||||
|
Shape shape,
|
||||||
|
int maxLevel) {
|
||||||
|
assert str.length() == level;
|
||||||
|
double w = levelW[level] / 2;
|
||||||
|
double h = levelH[level] / 2;
|
||||||
|
|
||||||
|
// Z-Order
|
||||||
|
// http://en.wikipedia.org/wiki/Z-order_%28curve%29
|
||||||
|
checkBattenberg('A', x - w, y + h, level, matches, str, shape, maxLevel);
|
||||||
|
checkBattenberg('B', x + w, y + h, level, matches, str, shape, maxLevel);
|
||||||
|
checkBattenberg('C', x - w, y - h, level, matches, str, shape, maxLevel);
|
||||||
|
checkBattenberg('D', x + w, y - h, level, matches, str, shape, maxLevel);
|
||||||
|
|
||||||
|
// possibly consider hilbert curve
|
||||||
|
// http://en.wikipedia.org/wiki/Hilbert_curve
|
||||||
|
// http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves
|
||||||
|
// if we actually use the range property in the query, this could be useful
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkBattenberg(
|
||||||
|
char c,
|
||||||
|
double cx,
|
||||||
|
double cy,
|
||||||
|
int level,
|
||||||
|
List<Node> matches,
|
||||||
|
StringBuilder str,
|
||||||
|
Shape shape,
|
||||||
|
int maxLevel) {
|
||||||
|
assert str.length() == level;
|
||||||
|
double w = levelW[level] / 2;
|
||||||
|
double h = levelH[level] / 2;
|
||||||
|
|
||||||
|
int strlen = str.length();
|
||||||
|
Rectangle rectangle = ctx.makeRect(cx - w, cx + w, cy - h, cy + h);
|
||||||
|
SpatialRelation v = shape.relate(rectangle, ctx);
|
||||||
|
if (SpatialRelation.CONTAINS == v) {
|
||||||
|
str.append(c);
|
||||||
|
//str.append(SpatialPrefixGrid.COVER);
|
||||||
|
matches.add(new QuadCell(str.toString(),v.transpose()));
|
||||||
|
} else if (SpatialRelation.DISJOINT == v) {
|
||||||
|
// nothing
|
||||||
|
} else { // SpatialRelation.WITHIN, SpatialRelation.INTERSECTS
|
||||||
|
str.append(c);
|
||||||
|
|
||||||
|
int nextLevel = level+1;
|
||||||
|
if (nextLevel >= maxLevel) {
|
||||||
|
//str.append(SpatialPrefixGrid.INTERSECTS);
|
||||||
|
matches.add(new QuadCell(str.toString(),v.transpose()));
|
||||||
|
} else {
|
||||||
|
build(cx, cy, nextLevel, matches, str, shape, maxLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
str.setLength(strlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
class QuadCell extends Node {
|
||||||
|
|
||||||
|
public QuadCell(String token) {
|
||||||
|
super(QuadPrefixTree.this, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public QuadCell(String token, SpatialRelation shapeRel) {
|
||||||
|
super(QuadPrefixTree.this, token);
|
||||||
|
this.shapeRel = shapeRel;
|
||||||
|
}
|
||||||
|
|
||||||
|
QuadCell(byte[] bytes, int off, int len) {
|
||||||
|
super(QuadPrefixTree.this, bytes, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset(byte[] bytes, int off, int len) {
|
||||||
|
super.reset(bytes, off, len);
|
||||||
|
shape = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Node> getSubCells() {
|
||||||
|
List<Node> cells = new ArrayList<Node>(4);
|
||||||
|
cells.add(new QuadCell(getTokenString()+"A"));
|
||||||
|
cells.add(new QuadCell(getTokenString()+"B"));
|
||||||
|
cells.add(new QuadCell(getTokenString()+"C"));
|
||||||
|
cells.add(new QuadCell(getTokenString()+"D"));
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSubCellsSize() {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getSubCell(Point p) {
|
||||||
|
return QuadPrefixTree.this.getNode(p,getLevel()+1);//not performant!
|
||||||
|
}
|
||||||
|
|
||||||
|
private Shape shape;//cache
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Shape getShape() {
|
||||||
|
if (shape == null)
|
||||||
|
shape = makeShape();
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rectangle makeShape() {
|
||||||
|
String token = getTokenString();
|
||||||
|
double xmin = QuadPrefixTree.this.xmin;
|
||||||
|
double ymin = QuadPrefixTree.this.ymin;
|
||||||
|
|
||||||
|
for (int i = 0; i < token.length(); i++) {
|
||||||
|
char c = token.charAt(i);
|
||||||
|
if ('A' == c || 'a' == c) {
|
||||||
|
ymin += levelH[i];
|
||||||
|
} else if ('B' == c || 'b' == c) {
|
||||||
|
xmin += levelW[i];
|
||||||
|
ymin += levelH[i];
|
||||||
|
} else if ('C' == c || 'c' == c) {
|
||||||
|
// nothing really
|
||||||
|
}
|
||||||
|
else if('D' == c || 'd' == c) {
|
||||||
|
xmin += levelW[i];
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("unexpected char: " + c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int len = token.length();
|
||||||
|
double width, height;
|
||||||
|
if (len > 0) {
|
||||||
|
width = levelW[len-1];
|
||||||
|
height = levelH[len-1];
|
||||||
|
} else {
|
||||||
|
width = gridW;
|
||||||
|
height = gridH;
|
||||||
|
}
|
||||||
|
return ctx.makeRect(xmin, xmin + width, ymin, ymin + height);
|
||||||
|
}
|
||||||
|
}//QuadCell
|
||||||
|
}
|
|
@ -0,0 +1,246 @@
|
||||||
|
/*
|
||||||
|
* 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.strategy.prefix.tree;
|
||||||
|
|
||||||
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
|
import com.spatial4j.core.shape.Point;
|
||||||
|
import com.spatial4j.core.shape.Shape;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Spatial Prefix Tree, or Trie, which decomposes shapes into prefixed strings at variable lengths corresponding to
|
||||||
|
* variable precision. Each string corresponds to a spatial region.
|
||||||
|
*
|
||||||
|
* Implementations of this class should be thread-safe and immutable once initialized.
|
||||||
|
*/
|
||||||
|
public abstract class SpatialPrefixTree {
|
||||||
|
|
||||||
|
protected static final Charset UTF8 = Charset.forName("UTF-8");
|
||||||
|
|
||||||
|
protected final int maxLevels;
|
||||||
|
|
||||||
|
protected final SpatialContext ctx;
|
||||||
|
|
||||||
|
public SpatialPrefixTree(SpatialContext ctx, int maxLevels) {
|
||||||
|
assert maxLevels > 0;
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.maxLevels = maxLevels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpatialContext getSpatialContext() {
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxLevels() {
|
||||||
|
return maxLevels;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + "(maxLevels:" + maxLevels + ",ctx:" + ctx + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link com.spatial4j.core.query.SpatialArgs#getDistPrecision()}.
|
||||||
|
* A grid level looked up via {@link #getLevelForDistance(double)} is returned.
|
||||||
|
*
|
||||||
|
* @param shape
|
||||||
|
* @param precision 0-0.5
|
||||||
|
* @return 1-maxLevels
|
||||||
|
*/
|
||||||
|
public int getMaxLevelForPrecision(Shape shape, double precision) {
|
||||||
|
if (precision < 0 || precision > 0.5) {
|
||||||
|
throw new IllegalArgumentException("Precision " + precision + " must be between [0-0.5]");
|
||||||
|
}
|
||||||
|
if (precision == 0 || shape instanceof Point) {
|
||||||
|
return maxLevels;
|
||||||
|
}
|
||||||
|
double bboxArea = shape.getBoundingBox().getArea();
|
||||||
|
if (bboxArea == 0) {
|
||||||
|
return maxLevels;
|
||||||
|
}
|
||||||
|
double avgSideLenFromCenter = Math.sqrt(bboxArea) / 2;
|
||||||
|
return getLevelForDistance(avgSideLenFromCenter * precision);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the level of the smallest grid size with a side length that is greater or equal to the provided
|
||||||
|
* distance.
|
||||||
|
*
|
||||||
|
* @param dist >= 0
|
||||||
|
* @return level [1-maxLevels]
|
||||||
|
*/
|
||||||
|
public abstract int getLevelForDistance(double dist);
|
||||||
|
|
||||||
|
//TODO double getDistanceForLevel(int level)
|
||||||
|
|
||||||
|
private transient Node worldNode;//cached
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the level 0 cell which encompasses all spatial data. Equivalent to {@link #getNode(String)} with "".
|
||||||
|
* This cell is threadsafe, just like a spatial prefix grid is, although cells aren't
|
||||||
|
* generally threadsafe.
|
||||||
|
* TODO rename to getTopCell or is this fine?
|
||||||
|
*/
|
||||||
|
public Node getWorldNode() {
|
||||||
|
if (worldNode == null) {
|
||||||
|
worldNode = getNode("");
|
||||||
|
}
|
||||||
|
return worldNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cell for the specified token. The empty string should be equal to {@link #getWorldNode()}.
|
||||||
|
* Precondition: Never called when token length > maxLevel.
|
||||||
|
*/
|
||||||
|
public abstract Node getNode(String token);
|
||||||
|
|
||||||
|
public abstract Node getNode(byte[] bytes, int offset, int len);
|
||||||
|
|
||||||
|
public final Node getNode(byte[] bytes, int offset, int len, Node target) {
|
||||||
|
if (target == null) {
|
||||||
|
return getNode(bytes, offset, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
target.reset(bytes, offset, len);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Node getNode(Point p, int level) {
|
||||||
|
return getNodes(p, level, false).get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the intersecting & including cells for the specified shape, without exceeding detail level.
|
||||||
|
* The result is a set of cells (no dups), sorted. Unmodifiable.
|
||||||
|
* <p/>
|
||||||
|
* This implementation checks if shape is a Point and if so uses an implementation that
|
||||||
|
* recursively calls {@link Node#getSubCell(com.spatial4j.core.shape.Point)}. Cell subclasses
|
||||||
|
* ideally implement that method with a quick implementation, otherwise, subclasses should
|
||||||
|
* override this method to invoke {@link #getNodesAltPoint(com.spatial4j.core.shape.Point, int, boolean)}.
|
||||||
|
* TODO consider another approach returning an iterator -- won't build up all cells in memory.
|
||||||
|
*/
|
||||||
|
public List<Node> getNodes(Shape shape, int detailLevel, boolean inclParents) {
|
||||||
|
if (detailLevel > maxLevels) {
|
||||||
|
throw new IllegalArgumentException("detailLevel > maxLevels");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Node> cells;
|
||||||
|
if (shape instanceof Point) {
|
||||||
|
//optimized point algorithm
|
||||||
|
final int initialCapacity = inclParents ? 1 + detailLevel : 1;
|
||||||
|
cells = new ArrayList<Node>(initialCapacity);
|
||||||
|
recursiveGetNodes(getWorldNode(), (Point) shape, detailLevel, true, cells);
|
||||||
|
assert cells.size() == initialCapacity;
|
||||||
|
} else {
|
||||||
|
cells = new ArrayList<Node>(inclParents ? 1024 : 512);
|
||||||
|
recursiveGetNodes(getWorldNode(), shape, detailLevel, inclParents, cells);
|
||||||
|
}
|
||||||
|
if (inclParents) {
|
||||||
|
Node c = cells.remove(0);//remove getWorldNode()
|
||||||
|
assert c.getLevel() == 0;
|
||||||
|
}
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recursiveGetNodes(Node node, Shape shape, int detailLevel, boolean inclParents,
|
||||||
|
Collection<Node> result) {
|
||||||
|
if (node.isLeaf()) {//cell is within shape
|
||||||
|
result.add(node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Collection<Node> subCells = node.getSubCells(shape);
|
||||||
|
if (node.getLevel() == detailLevel - 1) {
|
||||||
|
if (subCells.size() < node.getSubCellsSize()) {
|
||||||
|
if (inclParents)
|
||||||
|
result.add(node);
|
||||||
|
for (Node subCell : subCells) {
|
||||||
|
subCell.setLeaf();
|
||||||
|
}
|
||||||
|
result.addAll(subCells);
|
||||||
|
} else {//a bottom level (i.e. detail level) optimization where all boxes intersect, so use parent cell.
|
||||||
|
node.setLeaf();
|
||||||
|
result.add(node);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (inclParents) {
|
||||||
|
result.add(node);
|
||||||
|
}
|
||||||
|
for (Node subCell : subCells) {
|
||||||
|
recursiveGetNodes(subCell, shape, detailLevel, inclParents, result);//tail call
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recursiveGetNodes(Node node, Point point, int detailLevel, boolean inclParents,
|
||||||
|
Collection<Node> result) {
|
||||||
|
if (inclParents) {
|
||||||
|
result.add(node);
|
||||||
|
}
|
||||||
|
final Node pCell = node.getSubCell(point);
|
||||||
|
if (node.getLevel() == detailLevel - 1) {
|
||||||
|
pCell.setLeaf();
|
||||||
|
result.add(pCell);
|
||||||
|
} else {
|
||||||
|
recursiveGetNodes(pCell, point, detailLevel, inclParents, result);//tail call
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclasses might override {@link #getNodes(com.spatial4j.core.shape.Shape, int, boolean)}
|
||||||
|
* and check if the argument is a shape and if so, delegate
|
||||||
|
* to this implementation, which calls {@link #getNode(com.spatial4j.core.shape.Point, int)} and
|
||||||
|
* then calls {@link #getNode(String)} repeatedly if inclParents is true.
|
||||||
|
*/
|
||||||
|
protected final List<Node> getNodesAltPoint(Point p, int detailLevel, boolean inclParents) {
|
||||||
|
Node cell = getNode(p, detailLevel);
|
||||||
|
if (!inclParents) {
|
||||||
|
return Collections.singletonList(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
String endToken = cell.getTokenString();
|
||||||
|
assert endToken.length() == detailLevel;
|
||||||
|
List<Node> cells = new ArrayList<Node>(detailLevel);
|
||||||
|
for (int i = 1; i < detailLevel; i++) {
|
||||||
|
cells.add(getNode(endToken.substring(0, i)));
|
||||||
|
}
|
||||||
|
cells.add(cell);
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will add the trailing leaf byte for leaves. This isn't particularly efficient.
|
||||||
|
*/
|
||||||
|
public static List<String> nodesToTokenStrings(Collection<Node> nodes) {
|
||||||
|
List<String> tokens = new ArrayList<String>((nodes.size()));
|
||||||
|
for (Node node : nodes) {
|
||||||
|
final String token = node.getTokenString();
|
||||||
|
if (node.isLeaf()) {
|
||||||
|
tokens.add(token + (char) Node.LEAF_BYTE);
|
||||||
|
} else {
|
||||||
|
tokens.add(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* 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.strategy.prefix.tree;
|
||||||
|
|
||||||
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
|
import com.spatial4j.core.distance.DistanceUnits;
|
||||||
|
import com.spatial4j.core.distance.DistanceUtils;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dsmiley
|
||||||
|
*/
|
||||||
|
public abstract class SpatialPrefixTreeFactory {
|
||||||
|
|
||||||
|
private static final double DEFAULT_GEO_MAX_DETAIL_KM = 0.001;//1m
|
||||||
|
|
||||||
|
protected Map<String, String> args;
|
||||||
|
protected SpatialContext ctx;
|
||||||
|
protected Integer maxLevels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The factory is looked up via "prefixTree" in args, expecting "geohash" or "quad".
|
||||||
|
* If its neither of these, then "geohash" is chosen for a geo context, otherwise "quad" is chosen.
|
||||||
|
*/
|
||||||
|
public static SpatialPrefixTree makeSPT(Map<String,String> args, ClassLoader classLoader, SpatialContext ctx) {
|
||||||
|
SpatialPrefixTreeFactory instance;
|
||||||
|
String cname = args.get("prefixTree");
|
||||||
|
if (cname == null)
|
||||||
|
cname = ctx.isGeo() ? "geohash" : "quad";
|
||||||
|
if ("geohash".equalsIgnoreCase(cname))
|
||||||
|
instance = new GeohashPrefixTree.Factory();
|
||||||
|
else if ("quad".equalsIgnoreCase(cname))
|
||||||
|
instance = new QuadPrefixTree.Factory();
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
Class c = classLoader.loadClass(cname);
|
||||||
|
instance = (SpatialPrefixTreeFactory) c.newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
instance.init(args,ctx);
|
||||||
|
return instance.newSPT();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void init(Map<String, String> args, SpatialContext ctx) {
|
||||||
|
this.args = args;
|
||||||
|
this.ctx = ctx;
|
||||||
|
initMaxLevels();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initMaxLevels() {
|
||||||
|
String mlStr = args.get("maxLevels");
|
||||||
|
if (mlStr != null) {
|
||||||
|
maxLevels = Integer.valueOf(mlStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double degrees;
|
||||||
|
String maxDetailDistStr = args.get("maxDetailDist");
|
||||||
|
if (maxDetailDistStr == null) {
|
||||||
|
if (!ctx.isGeo()) {
|
||||||
|
return;//let default to max
|
||||||
|
}
|
||||||
|
degrees = DistanceUtils.dist2Degrees(DEFAULT_GEO_MAX_DETAIL_KM, DistanceUnits.KILOMETERS.earthRadius());
|
||||||
|
} else {
|
||||||
|
degrees = DistanceUtils.dist2Degrees(Double.parseDouble(maxDetailDistStr), ctx.getUnits().earthRadius());
|
||||||
|
}
|
||||||
|
maxLevels = getLevelForDistance(degrees) + 1;//returns 1 greater
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calls {@link SpatialPrefixTree#getLevelForDistance(double)}. */
|
||||||
|
protected abstract int getLevelForDistance(double degrees);
|
||||||
|
|
||||||
|
protected abstract SpatialPrefixTree newSPT();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Spatial Prefix package supports spatial indexing by index-time tokens
|
||||||
|
* where adding characters to a string gives greater resolution.
|
||||||
|
*
|
||||||
|
* Potential Implementations include:
|
||||||
|
* * http://en.wikipedia.org/wiki/Quadtree
|
||||||
|
* * http://en.wikipedia.org/wiki/Geohash
|
||||||
|
* * http://healpix.jpl.nasa.gov/
|
||||||
|
*/
|
||||||
|
package org.apache.lucene.spatial.strategy.prefix.tree;
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
package org.apache.lucene.spatial.strategy.prefix;
|
package org.apache.lucene.spatial.strategy.prefix;
|
||||||
|
|
||||||
import com.spatial4j.core.context.SpatialContext;
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
import com.spatial4j.core.prefix.geohash.GeohashPrefixTree;
|
|
||||||
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.GeohashPrefixTree;
|
||||||
import org.apache.lucene.spatial.SpatialMatchConcern;
|
import org.apache.lucene.spatial.SpatialMatchConcern;
|
||||||
import org.apache.lucene.spatial.StrategyTestCase;
|
import org.apache.lucene.spatial.StrategyTestCase;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
|
@ -22,11 +22,11 @@ import org.apache.lucene.document.Field;
|
||||||
import org.apache.lucene.document.StringField;
|
import org.apache.lucene.document.StringField;
|
||||||
import com.spatial4j.core.context.SpatialContext;
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
import com.spatial4j.core.context.simple.SimpleSpatialContext;
|
import com.spatial4j.core.context.simple.SimpleSpatialContext;
|
||||||
import com.spatial4j.core.prefix.quad.QuadPrefixTree;
|
|
||||||
import com.spatial4j.core.query.SpatialArgsParser;
|
import com.spatial4j.core.query.SpatialArgsParser;
|
||||||
import com.spatial4j.core.shape.Shape;
|
import com.spatial4j.core.shape.Shape;
|
||||||
import com.spatial4j.core.shape.simple.PointImpl;
|
import com.spatial4j.core.shape.simple.PointImpl;
|
||||||
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.QuadPrefixTree;
|
||||||
import org.apache.lucene.spatial.SpatialTestCase;
|
import org.apache.lucene.spatial.SpatialTestCase;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* 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.strategy.prefix.tree;
|
||||||
|
|
||||||
|
import com.spatial4j.core.context.simple.SimpleSpatialContext;
|
||||||
|
import com.spatial4j.core.shape.Rectangle;
|
||||||
|
import com.spatial4j.core.shape.Shape;
|
||||||
|
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.GeohashPrefixTree;
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.Node;
|
||||||
|
import org.apache.lucene.spatial.strategy.prefix.tree.SpatialPrefixTree;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
|
||||||
|
public class SpatialPrefixTreeTest {
|
||||||
|
|
||||||
|
//TODO plug in others and test them
|
||||||
|
private SimpleSpatialContext ctx;
|
||||||
|
private SpatialPrefixTree trie;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
ctx = SimpleSpatialContext.GEO_KM;
|
||||||
|
trie = new GeohashPrefixTree(ctx,4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNodeTraverse() {
|
||||||
|
Node prevN = null;
|
||||||
|
Node n = trie.getWorldNode();
|
||||||
|
assertEquals(0,n.getLevel());
|
||||||
|
assertEquals(ctx.getWorldBounds(),n.getShape());
|
||||||
|
while(n.getLevel() < trie.getMaxLevels()) {
|
||||||
|
prevN = n;
|
||||||
|
n = n.getSubCells().iterator().next();//TODO random which one?
|
||||||
|
|
||||||
|
assertEquals(prevN.getLevel()+1,n.getLevel());
|
||||||
|
Rectangle prevNShape = (Rectangle) prevN.getShape();
|
||||||
|
Shape s = n.getShape();
|
||||||
|
Rectangle sbox = s.getBoundingBox();
|
||||||
|
assertTrue(prevNShape.getWidth() > sbox.getWidth());
|
||||||
|
assertTrue(prevNShape.getHeight() > sbox.getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ import com.spatial4j.core.context.ParseUtils;
|
||||||
import com.spatial4j.core.context.SpatialContext;
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
import com.spatial4j.core.context.simple.SimpleSpatialContext;
|
import com.spatial4j.core.context.simple.SimpleSpatialContext;
|
||||||
import com.spatial4j.core.exception.InvalidShapeException;
|
import com.spatial4j.core.exception.InvalidShapeException;
|
||||||
import com.spatial4j.core.prefix.geohash.GeohashUtils;
|
import com.spatial4j.core.util.GeohashUtils;
|
||||||
import com.spatial4j.core.shape.Point;
|
import com.spatial4j.core.shape.Point;
|
||||||
import org.apache.solr.common.SolrException;
|
import org.apache.solr.common.SolrException;
|
||||||
import org.apache.solr.response.TextResponseWriter;
|
import org.apache.solr.response.TextResponseWriter;
|
||||||
|
|
|
@ -19,7 +19,7 @@ package org.apache.solr.search.function.distance;
|
||||||
import org.apache.lucene.index.AtomicReaderContext;
|
import org.apache.lucene.index.AtomicReaderContext;
|
||||||
import org.apache.lucene.queries.function.FunctionValues;
|
import org.apache.lucene.queries.function.FunctionValues;
|
||||||
import org.apache.lucene.queries.function.ValueSource;
|
import org.apache.lucene.queries.function.ValueSource;
|
||||||
import com.spatial4j.core.prefix.geohash.GeohashUtils;
|
import com.spatial4j.core.util.GeohashUtils;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import com.spatial4j.core.context.simple.SimpleSpatialContext;
|
||||||
import com.spatial4j.core.distance.DistanceCalculator;
|
import com.spatial4j.core.distance.DistanceCalculator;
|
||||||
import com.spatial4j.core.distance.DistanceUnits;
|
import com.spatial4j.core.distance.DistanceUnits;
|
||||||
import com.spatial4j.core.distance.GeodesicSphereDistCalc;
|
import com.spatial4j.core.distance.GeodesicSphereDistCalc;
|
||||||
import com.spatial4j.core.prefix.geohash.GeohashUtils;
|
import com.spatial4j.core.util.GeohashUtils;
|
||||||
import com.spatial4j.core.shape.Point;
|
import com.spatial4j.core.shape.Point;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
|
@ -17,7 +17,7 @@ package org.apache.solr.search.function.distance;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import com.spatial4j.core.distance.DistanceUtils;
|
import com.spatial4j.core.distance.DistanceUtils;
|
||||||
import com.spatial4j.core.prefix.geohash.GeohashUtils;
|
import com.spatial4j.core.util.GeohashUtils;
|
||||||
import org.apache.solr.SolrTestCaseJ4;
|
import org.apache.solr.SolrTestCaseJ4;
|
||||||
import org.apache.solr.common.SolrException;
|
import org.apache.solr.common.SolrException;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
AnyObjectId[41698d61caf8eee1afdca7d39042a2eee1517ff5] was removed in git history.
|
AnyObjectId[6d3c8ca133496a20135a1d0fdb7f77811794f899] was removed in git history.
|
||||||
Apache SVN contains full history.
|
Apache SVN contains full history.
|
Loading…
Reference in New Issue