--- /dev/null
+/**
+ * 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.tier;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+import org.apache.lucene.search.Filter;
+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;
+
+
+/**
+ * <p><font color="red"><b>NOTE:</b> This API is still in
+ * flux and might change in incompatible ways in the next
+ * release.</font>
+ */
+public class CartesianPolyFilterBuilder {
+
+ // Finer granularity than 1 mile isn't accurate with
+ // 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;
+ /**
+ *
+ * @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 ) {
+ this.tierPrefix = tierPrefix;
+ this.minTier = minTierIndexed;
+ this.maxTier = maxTierIndexed;
+ }
+
+ 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();
+
+ 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;
+ }
+ //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;
+ }
+
+ //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 ;
+ 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)
+ {
+ Shape shape = getBoxShape(latitude, longitude, miles);
+ return new CartesianShapeFilter(shape, shape.getTierId());
+ }
+}