Index: contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesianShapeFilter.java =================================================================== --- contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesianShapeFilter.java (révision 930484) +++ contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesianShapeFilter.java (copie de travail) @@ -30,7 +30,7 @@ public class TestCartesianShapeFilter extends TestCase { public void testSerializable() throws IOException { - CartesianShapeFilter filter = new CartesianShapeFilter(new Shape("1"), + CartesianShapeFilter filter = new CartesianShapeFilter(new Shape(1), "test"); try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/CartesianTierPlotter.java =================================================================== --- contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/CartesianTierPlotter.java (révision 930484) +++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/CartesianTierPlotter.java (copie de travail) @@ -17,6 +17,8 @@ package org.apache.lucene.spatial.tier.projections; +import org.apache.lucene.spatial.geometry.DistanceUnits; + /** *

NOTE: This API is still in * flux and might change in incompatible ways in the next @@ -32,118 +34,115 @@ final IProjector projector; final String fieldPrefix; Double idd = Double.valueOf(180); - - public CartesianTierPlotter (int tierLevel, IProjector projector, String fieldPrefix) { - - this.tierLevel = tierLevel; + private static final double LOG_2 = Math.log(2); + + public CartesianTierPlotter(int tierLevel, IProjector projector, + String fieldPrefix) { + + this.tierLevel = tierLevel; this.projector = projector; this.fieldPrefix = fieldPrefix; - + setTierLength(); setTierBoxes(); setTierVerticalPosDivider(); } - - private void setTierLength (){ - this.tierLength = (int) Math.pow(2 , this.tierLevel); + + public CartesianTierPlotter(double radius, IProjector projector, + String fieldPrefix) { + this(CartesianTierPlotter.bestFit(radius), projector, fieldPrefix); } - - private void setTierBoxes () { - this.tierBoxes = (int)Math.pow(this.tierLength, 2); + + private void setTierLength() { + this.tierLength = (int) Math.pow(2, this.tierLevel); } - + + private void setTierBoxes() { + this.tierBoxes = (int) Math.pow(this.tierLength, 2); + } + /** - * Get nearest max power of 10 greater than - * the tierlen - * e.g - * tierId of 13 has tierLen 8192 - * nearest max power of 10 greater than tierLen - * would be 10,000 + * Get nearest max power of 10 greater than the tierlen e.g tierId of 13 has + * tierLen 8192 nearest max power of 10 greater than tierLen would be 10,000 */ - + private void setTierVerticalPosDivider() { - + // ceiling of log base 10 of tierLen - - tierVerticalPosDivider = Double.valueOf(Math.ceil( - Math.log10(Integer.valueOf(this.tierLength).doubleValue()))).intValue(); - + + tierVerticalPosDivider = Double.valueOf( + Math.ceil(Math.log10(Integer.valueOf(this.tierLength).doubleValue()))) + .intValue(); + // - tierVerticalPosDivider = (int)Math.pow(10, tierVerticalPosDivider ); - + tierVerticalPosDivider = (int) Math.pow(10, tierVerticalPosDivider); + } - - public double getTierVerticalPosDivider(){ + + public double getTierVerticalPosDivider() { return tierVerticalPosDivider; } - + /** - * TierBoxId is latitude box id + longitude box id - * where latitude box id, and longitude box id are transposed in to position - * coordinates. + * TierBoxId is latitude box id + longitude box id where latitude box id, and + * longitude box id are transposed in to position coordinates. * * @param latitude * @param longitude */ - public double getTierBoxId (double latitude, double longitude) { - + public double getTierBoxId(double latitude, double longitude) { + double[] coords = projector.coords(latitude, longitude); - - double id = getBoxId(coords[0]) + (getBoxId(coords[1]) / tierVerticalPosDivider); - return id ; + + double id = getBoxId(coords[0]) + + (getBoxId(coords[1]) / tierVerticalPosDivider); + return id; } - - - private double getBoxId (double coord){ - - + + private double getBoxId(double coord) { + return Math.floor(coord / (idd / this.tierLength)); } - + @SuppressWarnings("unused") - private double getBoxId (double coord, int tierLen){ - return Math.floor(coord / (idd / tierLen) ); + private double getBoxId(double coord, int tierLen) { + return Math.floor(coord / (idd / tierLen)); } + /** - * get the string name representing current tier - * _localTier<tiedId> + * get the string name representing current tier _localTier<tiedId> */ - public String getTierFieldName (){ - + public String getTierFieldName() { + return fieldPrefix + this.tierLevel; } - + /** - * get the string name representing tierId - * _localTier<tierId> + * get the string name representing tierId _localTier<tierId> + * * @param tierId */ - public String getTierFieldName (int tierId){ - + public String getTierFieldName(int tierId) { + return fieldPrefix + tierId; } - + /** - * Find the tier with the best fit for a bounding box - * Best fit is defined as the ceiling of - * log2 (circumference of earth / distance) - * distance is defined as the smallest box fitting - * the corner between a radius and a bounding box. - * - * Distances less than a mile return 15, finer granularity is - * in accurate + * Find the tier with the best fit for a bounding box Best fit is defined as + * the ceiling of log2 (circumference of earth / distance) distance is defined + * as the smallest box fitting the corner between a radius and a bounding box. + * + * Distances less than a mile return 15, finer granularity is in accurate */ - public int bestFit(double miles){ - - //28,892 a rough circumference of the earth - int circ = 28892; - - double r = miles / 2.0; - - double corner = r - Math.sqrt(Math.pow(r, 2) / 2.0d); - double times = circ / corner; - int bestFit = (int)Math.ceil(log2(times)) + 1; - + static public int bestFit(double range) { + return bestFit(range, DistanceUnits.MILES); + } + + static public int bestFit(double range, DistanceUnits distanceUnit) { + double times = distanceUnit.earthCircumference() / (2.0d * range); + + int bestFit = (int) Math.ceil(log2(times)); + if (bestFit > 15) { // 15 is the granularity of about 1 mile // finer granularity isn't accurate with standard java math @@ -151,14 +150,25 @@ } return bestFit; } - + /** - * a log to the base 2 formula - * Math.log(value) / Math.log(2) + * Computes log to base 2 of the given value + * * @param value + * Value to compute the log of + * @return Log_2 of the value */ - public double log2(double value) { - - return Math.log(value) / Math.log(2); + public static double log2(double value) { + return Math.log(value) / LOG_2; } + + /** + * Returns the ID of the tier level plotting is occuring at + * + * @return ID of the tier level plotting is occuring at + */ + public int getTierLevelId() { + return this.tierLevel; + } + } Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/Shape.java =================================================================== --- contrib/spatial/src/java/org/apache/lucene/spatial/tier/Shape.java (révision 930484) +++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/Shape.java (copie de travail) @@ -29,9 +29,9 @@ public class Shape implements Serializable{ private List area = new ArrayList(); - private String tierId; + private int tierId; - public Shape (String tierId){ + public Shape (int tierId){ this.tierId = tierId; } @@ -43,7 +43,7 @@ return area; } - public String getTierId(){ + public int getTierId(){ return tierId; } Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianPolyFilterBuilder.java =================================================================== --- contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianPolyFilterBuilder.java (révision 930484) +++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianPolyFilterBuilder.java (copie de travail) @@ -17,168 +17,123 @@ package org.apache.lucene.spatial.tier; -import java.math.BigDecimal; -import java.math.RoundingMode; - import org.apache.lucene.search.Filter; +import org.apache.lucene.spatial.geometry.FloatLatLng; +import org.apache.lucene.spatial.geometry.LatLng; +import org.apache.lucene.spatial.geometry.shape.LLRect; import org.apache.lucene.spatial.tier.projections.CartesianTierPlotter; import org.apache.lucene.spatial.tier.projections.IProjector; import org.apache.lucene.spatial.tier.projections.SinusoidalProjector; -import org.apache.lucene.spatial.geometry.LatLng; -import org.apache.lucene.spatial.geometry.FloatLatLng; -import org.apache.lucene.spatial.geometry.shape.LLRect; - /** - *

NOTE: This API is still in - * flux and might change in incompatible ways in the next - * release. + *

+ * NOTE: This API is still in flux and might change in + * incompatible ways in the next release. */ public class CartesianPolyFilterBuilder { // Finer granularity than 1 mile isn't accurate with - // standard java math. Also, there's already a 2nd + // standard java math. Also, there's already a 2nd // precise filter, if needed, in DistanceQueryBuilder, // that will make the filtering exact. public static final double MILES_FLOOR = 1.0; private IProjector projector = new SinusoidalProjector(); private final String tierPrefix; - private int minTier; - private int maxTier; + private int minTier; + private int maxTier; + /** * - * @param tierPrefix The prefix for the name of the fields containing the tier info - * @param minTierIndexed The minimum tier level indexed - * @param maxTierIndexed The maximum tier level indexed + * @param tierPrefix + * The prefix for the name of the fields containing the tier info + * @param minTierIndexed + * The minimum tier level indexed + * @param maxTierIndexed + * The maximum tier level indexed */ - public CartesianPolyFilterBuilder( String tierPrefix, int minTierIndexed, int maxTierIndexed ) { + public CartesianPolyFilterBuilder(String tierPrefix, int minTierIndexed, + int maxTierIndexed) { this.tierPrefix = tierPrefix; - this.minTier = minTierIndexed; - this.maxTier = maxTierIndexed; + this.minTier = minTierIndexed; + this.maxTier = maxTierIndexed; } - - public Shape getBoxShape(double latitude, double longitude, double miles) - { + + public Shape getBoxShape(double latitude, double longitude, double miles) { if (miles < MILES_FLOOR) { miles = MILES_FLOOR; } - LLRect box1 = LLRect.createBox( new FloatLatLng( latitude, longitude ), miles, miles ); - LatLng ll = box1.getLowerLeft(); - LatLng ur = box1.getUpperRight(); + LLRect box1 = LLRect.createBox(new FloatLatLng(latitude, longitude), miles, + miles); + LatLng lowerLeft = box1.getLowerLeft(); + LatLng upperRight = box1.getUpperRight(); - double latY = ur.getLat(); - double latX = ll.getLat(); - double longY = ur.getLng(); - double longX = ll.getLng(); - double longX2 = 0.0; - //These two if checks setup us up to deal with issues around the prime meridian and the 180th meridian - //In these two cases, we need to get tiles (tiers) from the lower left up to the meridian and then - //from the meridan to the upper right - //Are we crossing the 180 deg. longitude, if so, we need to do some special things - if (ur.getLng() < 0.0 && ll.getLng() > 0.0) { - longX2 = ll.getLng(); - longX = -180.0; + double latUpperRight = upperRight.getLat(); + double latLowerLeft = lowerLeft.getLat(); + double longUpperRight = upperRight.getLng(); + double longLowerLeft = lowerLeft.getLng(); + + CartesianTierPlotter ctp = new CartesianTierPlotter(miles, projector, + tierPrefix); + Shape shape = new Shape(ctp.getTierLevelId()); + + if (longUpperRight < longLowerLeft) { // Box cross the 180 meridian + addBoxes(shape, ctp, latLowerLeft, longLowerLeft, latUpperRight, + LatLng.LONGITUDE_DEGREE_RANGE / 2); + addBoxes(shape, ctp, latLowerLeft, -LatLng.LONGITUDE_DEGREE_RANGE / 2, + latUpperRight, longUpperRight); + } else { + addBoxes(shape, ctp, latLowerLeft, longLowerLeft, latUpperRight, + longUpperRight); } - //are we crossing the prime meridian (0 degrees)? If so, we need to account for it and boxes on both sides - if (ur.getLng() > 0.0 && ll.getLng() < 0.0) { - longX2 = ll.getLng(); - longX = 0.0; + + return shape; + } + + private void addBoxes(Shape shape, CartesianTierPlotter tierPlotter, + double lat1, double long1, double lat2, double long2) { + double boxId1 = tierPlotter.getTierBoxId(lat1, long1); + double boxId2 = tierPlotter.getTierBoxId(lat2, long2); + + double tierVert = tierPlotter.getTierVerticalPosDivider(); + + int LongIndex1 = (int) Math.round(boxId1); + int LatIndex1 = (int) Math.round((boxId1 - LongIndex1) * tierVert); + + int LongIndex2 = (int) Math.round(boxId2); + int LatIndex2 = (int) Math.round((boxId2 - LongIndex2) * tierVert); + + int startLong, endLong; + int startLat, endLat; + + if (LongIndex1 > LongIndex2) { + startLong = LongIndex2; + endLong = LongIndex1; + } else { + startLong = LongIndex1; + endLong = LongIndex2; } - - //System.err.println("getBoxShape:"+latY+"," + longY); - //System.err.println("getBoxShape:"+latX+"," + longX); - CartesianTierPlotter ctp = new CartesianTierPlotter(2, projector,tierPrefix); - int bestFit = ctp.bestFit(miles); - if (bestFit < minTier){ - bestFit = minTier; - } else if (bestFit > maxTier){ - bestFit = maxTier; - } - - ctp = new CartesianTierPlotter(bestFit, projector,tierPrefix); - Shape shape = new Shape(ctp.getTierFieldName()); - - // generate shape - // iterate from startX->endX - // iterate from startY -> endY - // shape.add(currentLat.currentLong); - //for the edge cases (prime meridian and the 180th meridian), this call handles all tiles East of the meridian - //for all other cases, it handles the whole set of tiles - shape = getShapeLoop(shape,ctp,latX,longX,latY,longY); - if (longX2 != 0.0) { - if (longX == 0.0) { - longX = longX2; - longY = 0.0; - //handles the lower left longitude to the prime meridian - //shape = getShapeLoop(shape, ctp, latX, longX, latY, longY); - } else { - //this clause handles the lower left longitude up to the 180 meridian - longX = longX2; - longY = 180.0; - } - shape = getShapeLoop(shape, ctp, latX, longX, latY, longY); - //System.err.println("getBoxShape2:"+latY+"," + longY); - //System.err.println("getBoxShape2:"+latX+"," + longX); - } - - - return shape; - } - - public Shape getShapeLoop(Shape shape, CartesianTierPlotter ctp, double latX, double longX, double latY, double longY) - { - - //System.err.println("getShapeLoop:"+latY+"," + longY); - //System.err.println("getShapeLoop:"+latX+"," + longX); - double beginAt = ctp.getTierBoxId(latX, longX); - double endAt = ctp.getTierBoxId(latY, longY); - if (beginAt > endAt){ - double tmp = beginAt; - beginAt = endAt; - endAt = tmp; - } - double tierVert = ctp.getTierVerticalPosDivider(); - //System.err.println(" | "+ beginAt+" | "+ endAt); - - double startX = beginAt - (beginAt %1); - double startY = beginAt - startX ; //should give a whole number - - double endX = endAt - (endAt %1); - double endY = endAt -endX; //should give a whole number - - int scale = (int)Math.log10(tierVert); - endY = new BigDecimal(endY).setScale(scale, RoundingMode.HALF_EVEN).doubleValue(); - startY = new BigDecimal(startY).setScale(scale, RoundingMode.HALF_EVEN).doubleValue(); - double xInc = 1.0d / tierVert; - xInc = new BigDecimal(xInc).setScale(scale, RoundingMode.HALF_EVEN).doubleValue(); - - //System.err.println("go from startX:"+startX+" to:" + endX); - for (; startX <= endX; startX++){ - - double itY = startY; - //System.err.println("go from startY:"+startY+" to:" + endY); - while (itY <= endY){ - //create a boxId - // startX.startY - double boxId = startX + itY ; + if (LatIndex1 > LatIndex2) { + startLat = LatIndex2; + endLat = LatIndex1; + } else { + startLat = LatIndex1; + endLat = LatIndex2; + } + + int LatIndex, LongIndex; + for (LongIndex = startLong; LongIndex <= endLong; LongIndex++) { + for (LatIndex = startLat; LatIndex <= endLat; LatIndex++) { + // create a boxId + double boxId = LongIndex + LatIndex / tierVert; shape.addBox(boxId); - //System.err.println("----"+startX+" and "+itY); - //System.err.println("----"+boxId); - itY += xInc; - - // java keeps 0.0001 as 1.0E-1 - // which ends up as 0.00011111 - itY = new BigDecimal(itY).setScale(scale, RoundingMode.HALF_EVEN).doubleValue(); } } - return shape; } - - public Filter getBoundingArea(double latitude, double longitude, double miles) - { + + public Filter getBoundingArea(double latitude, double longitude, double miles) { Shape shape = getBoxShape(latitude, longitude, miles); - return new CartesianShapeFilter(shape, shape.getTierId()); + return new CartesianShapeFilter(shape, tierPrefix + shape.getTierId()); } } Index: contrib/spatial/src/java/org/apache/lucene/spatial/geometry/LatLng.java =================================================================== --- contrib/spatial/src/java/org/apache/lucene/spatial/geometry/LatLng.java (révision 930484) +++ contrib/spatial/src/java/org/apache/lucene/spatial/geometry/LatLng.java (copie de travail) @@ -30,6 +30,13 @@ */ public abstract class LatLng { + public final static int LONGITUDE_DEGREE_RANGE = 360; + public final static int LONGITUDE_DEGREE_MIN = -LONGITUDE_DEGREE_RANGE / 2; + public final static int LONGITUDE_DEGREE_MAX = LONGITUDE_DEGREE_RANGE / 2; + public final static int LATITUDE_DEGREE_RANGE = 180; + public final static int LATITUDE_DEGREE_MIN = -LATITUDE_DEGREE_RANGE / 2; + public final static int LATITUDE_DEGREE_MAX = LATITUDE_DEGREE_RANGE / 2; + public abstract boolean isNormalized(); public abstract boolean isFixedPoint();