/* * Hibernate, Relational Persistence for Idiomatic Java * * JBoss, Home of Professional Open Source * Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors * as indicated by the @authors tag. All rights reserved. * See the copyright.txt in the distribution for a * full listing of individual contributors. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package org.hibernate.search.spatial.impl; import java.util.ArrayList; import java.util.List; /** * Spatial fields, ids generator and geometric calculation methods for use in SpatialFieldBridge * * @author Nicolas Helleringer <nicolas.helleringer@novacodex.net> * @author Mathieu Perez <mathieu.perez@novacodex.net> * @see org.hibernate.search.spatial.SpatialFieldBridgeByQuadTree * @see org.hibernate.search.spatial.SpatialFieldBridgeByRange */ public abstract class SpatialHelper { private static final double LOG2 = Math.log( 2 ); /** * Private constructor locking down utility class */ private SpatialHelper() {}; /** * Generate a Cell Index on one axis * * @param coordinate position to compute the Index for * @param range range of the axis (-pi,pi)/(-90,90) => 2*pi/180 e.g * @param quadTreeLevel Hox many time the range has been split in two * @return the cell index on the axis */ public static int getCellIndex(double coordinate, double range, int quadTreeLevel) { return ( int ) Math.floor( Math.pow( 2, quadTreeLevel ) * coordinate / range ); } /** * Generate a Quad Tree Cell Id (with both Cell Index on both dimension in it) for a position * * @param point position to compute the Quad Tree Cell Id for * @param quadTreeLevel Hox many time the dimensions have been split in two * @return the cell id for the point at the given quad tree level */ public static String getQuadTreeCellId(Point point, int quadTreeLevel) { double[] indexablesCoordinates = projectToIndexSpace( point ); int longitudeCellIndex = getCellIndex( indexablesCoordinates[0], GeometricConstants.PROJECTED_LONGITUDE_RANGE, quadTreeLevel ); int latitudeCellIndex = getCellIndex( indexablesCoordinates[1], GeometricConstants.PROJECTED_LATITUDE_RANGE, quadTreeLevel ); return formatQuadTreeCellId(longitudeCellIndex, latitudeCellIndex); } /** * Generate a Quad Tree Cell Ids List covered by a bounding box * * @param lowerLeft lower left corner of the bounding box * @param upperRight upper right corner of the bounding box * @param quadTreeLevel quad tree level of the wanted cell ids * @return List of ids of the cells containing the point */ public static List<String> getQuadTreeCellsIds(Point lowerLeft, Point upperRight, int quadTreeLevel) { double[] projectedLowerLeft = projectToIndexSpace( lowerLeft ); int lowerLeftXIndex = getCellIndex( projectedLowerLeft[0], GeometricConstants.PROJECTED_LONGITUDE_RANGE, quadTreeLevel ); int lowerLeftYIndex = getCellIndex( projectedLowerLeft[1], GeometricConstants.PROJECTED_LATITUDE_RANGE, quadTreeLevel ); double[] projectedUpperRight = projectToIndexSpace( upperRight ); int upperRightXIndex = getCellIndex( projectedUpperRight[0], GeometricConstants.PROJECTED_LONGITUDE_RANGE, quadTreeLevel ); int upperRightYIndex = getCellIndex( projectedUpperRight[1], GeometricConstants.PROJECTED_LATITUDE_RANGE, quadTreeLevel ); double[] projectedLowerRight= projectToIndexSpace( Point.fromDegrees( lowerLeft.getLatitude(), upperRight.getLongitude() ) ); int lowerRightXIndex = getCellIndex( projectedLowerRight[0], GeometricConstants.PROJECTED_LONGITUDE_RANGE, quadTreeLevel ); int lowerRightYIndex = getCellIndex( projectedLowerRight[1], GeometricConstants.PROJECTED_LATITUDE_RANGE, quadTreeLevel ); double[] projectedUpperLeft = projectToIndexSpace( Point.fromDegrees( upperRight.getLatitude(), lowerLeft.getLongitude() ) ); int upperLeftXIndex = getCellIndex( projectedUpperLeft[0], GeometricConstants.PROJECTED_LONGITUDE_RANGE, quadTreeLevel ); int upperLeftYIndex = getCellIndex( projectedUpperLeft[1], GeometricConstants.PROJECTED_LATITUDE_RANGE, quadTreeLevel ); int startX, endX; startX= Math.min(Math.min(Math.min(lowerLeftXIndex,upperLeftXIndex),upperRightXIndex),lowerRightXIndex); endX= Math.max(Math.max(Math.max(lowerLeftXIndex,upperLeftXIndex),upperRightXIndex),lowerRightXIndex); int startY, endY; startY= Math.min(Math.min(Math.min(lowerLeftYIndex,upperLeftYIndex),upperRightYIndex),lowerRightYIndex); endY= Math.max(Math.max(Math.max(lowerLeftYIndex,upperLeftYIndex),upperRightYIndex),lowerRightYIndex); List<String> quadTreeCellsIds = new ArrayList<String>((endX+1-startX)*(endY+1-startY)); int xIndex, yIndex; for ( xIndex = startX; xIndex <= endX; xIndex++ ) { for ( yIndex = startY; yIndex <= endY; yIndex++ ) { quadTreeCellsIds.add(formatQuadTreeCellId(xIndex, yIndex)); } } return quadTreeCellsIds; } /** * Generate a Quad Tree Cell Ids List for the bounding box of a circular search area * * @param center center of the search area * @param radius radius of the search area * @param quadTreeLevel Quad Tree level of the wanted cell ids * @return List of the ids of the cells covering the bounding box of the given search discus */ public static List<String> getQuadTreeCellsIds(Point center, double radius, int quadTreeLevel) { Rectangle boundingBox = Rectangle.fromBoundingCircle( center, radius ); double lowerLeftLatitude = boundingBox.getLowerLeft().getLatitude(); double lowerLeftLongitude = boundingBox.getLowerLeft().getLongitude(); double upperRightLatitude = boundingBox.getUpperRight().getLatitude(); double upperRightLongitude = boundingBox.getUpperRight().getLongitude(); if ( upperRightLongitude < lowerLeftLongitude ) { // Box cross the 180 meridian final List<String> quadTreeCellsIds; quadTreeCellsIds = getQuadTreeCellsIds( Point.fromDegreesInclusive( lowerLeftLatitude, lowerLeftLongitude ), Point.fromDegreesInclusive( upperRightLatitude, GeometricConstants.LONGITUDE_DEGREE_RANGE / 2 ), quadTreeLevel ); quadTreeCellsIds.addAll( getQuadTreeCellsIds( Point.fromDegreesInclusive( lowerLeftLatitude, GeometricConstants.LONGITUDE_DEGREE_RANGE / 2 ), Point.fromDegreesInclusive( upperRightLatitude, upperRightLongitude ), quadTreeLevel ) ); return quadTreeCellsIds; } else { return getQuadTreeCellsIds( Point.fromDegreesInclusive( lowerLeftLatitude, lowerLeftLongitude ), Point.fromDegreesInclusive( upperRightLatitude, upperRightLongitude ), quadTreeLevel ); } } /** * If point are searched at d distance from a point, a certain quad tree cell level will problem quad tree cell * that are big enough to contain the search area but the smallest possible. By returning this level we ensure * 4 Quad Tree Cell maximum will be needed to cover the search area (2 max on each axis because of search area * crossing fixed bonds of the quad tree cells) * * @param searchRange * search range to be covered by the quad tree cells * @return Return the best Quad Tree level for a given search radius. */ public static int findBestQuadTreeLevelForSearchRange(double searchRange) { double iterations = GeometricConstants.EARTH_EQUATOR_CIRCUMFERENCE_KM / ( 2.0d * searchRange ); return ( int ) Math.max( 0, Math.ceil( Math.log( iterations ) / LOG2 ) ); } /** * Project a degree latitude/longitude point into a sinusoidal projection planar space for quad tree cell ids * computation * * @param point * point to be projected * @return array of projected coordinates */ public static double[] projectToIndexSpace(Point point) { double[] projectedCoordinates = new double[2]; projectedCoordinates[0] = point.getLongitudeRad() * Math.cos( point.getLatitudeRad() ); projectedCoordinates[1] = point.getLatitudeRad(); return projectedCoordinates; } public static String formatFieldName(final int quadTreeLevel, final String fieldName) { return fieldName + "_HSSI_" + quadTreeLevel; } public static String formatLatitude(final String fieldName) { return fieldName + "_HSSI_Latitude"; } public static String formatLongitude(final String fieldName) { return fieldName + "_HSSI_Longitude"; } public static String formatQuadTreeCellId(final int xIndex, final int yIndex) { return xIndex + "|" + yIndex; } }