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.context.SpatialContext;
import com.spatial4j.core.io.GeohashUtils;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* A {@link SpatialPrefixTree} based on
* <a href="http://en.wikipedia.org/wiki/Geohash">Geohashes</a>.
* Uses {@link GeohashUtils} to do all the geohash work.
*
* @lucene.experimental
*/
public class GeohashPrefixTree extends SpatialPrefixTree {
/**
* Factory for creating {@link GeohashPrefixTree} instances with useful defaults
*/
public static class Factory extends SpatialPrefixTreeFactory {
@Override
protected int getLevelForDistance(double degrees) {
GeohashPrefixTree grid = new GeohashPrefixTree(ctx, GeohashPrefixTree.getMaxLevelsPossible());
return grid.getLevelForDistance(degrees);
}
@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) {
if (dist == 0)
return maxLevels;//short circuit
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
}