/**
* 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.projections;
/**
* <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 CartesianTierPlotter {
public static final String DEFALT_FIELD_PREFIX = "_tier_";
final int tierLevel;
int tierLength;
int tierBoxes;
int tierVerticalPosDivider;
final IProjector projector;
final String fieldPrefix;
Double idd = Double.valueOf(180);
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);
}
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
*/
private void setTierVerticalPosDivider() {
// ceiling of log base 10 of tierLen
tierVerticalPosDivider = Double.valueOf(Math.ceil(
Math.log10(Integer.valueOf(this.tierLength).doubleValue()))).intValue();
//
tierVerticalPosDivider = (int)Math.pow(10, tierVerticalPosDivider );
}
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.
*
* @param latitude
* @param 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 ;
}
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) );
}
/**
* get the string name representing current tier
* _localTier<tiedId>
*/
public String getTierFieldName (){
return fieldPrefix + this.tierLevel;
}
/**
* get the string name representing tierId
* _localTier<tierId>
* @param 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
*/
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;
if (bestFit > 15) {
// 15 is the granularity of about 1 mile
// finer granularity isn't accurate with standard java math
return 15;
}
return bestFit;
}
/**
* a log to the base 2 formula
* <code>Math.log(value) / Math.log(2)</code>
* @param value
*/
public double log2(double value) {
return Math.log(value) / Math.log(2);
}
}