From 581c82ccbee69198633ba78ffd251261015b6f89 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 30 Dec 2008 07:37:17 +0000 Subject: [PATCH] LUCENE-1387 -- adding locallucene as new spatial contrib package. git-svn-id: https://svn.apache.org/repos/asf/lucene/java/trunk@730067 13f79535-47bb-0310-9956-ffa450edef68 --- contrib/spatial/build.xml | 32 +++ contrib/spatial/pom.xml.template | 50 ++++ .../lucene/spatial/ISerialChainFilter.java | 34 +++ .../apache/lucene/spatial/NumberUtils.java | 68 +++++ .../lucene/spatial/SerialChainFilter.java | 199 ++++++++++++++ .../spatial/geometry/CartesianPoint.java | 56 ++++ .../spatial/geometry/DistanceUnits.java | 23 ++ .../lucene/spatial/geometry/FixedLatLng.java | 135 ++++++++++ .../lucene/spatial/geometry/FloatLatLng.java | 116 ++++++++ .../lucene/spatial/geometry/LatLng.java | 164 ++++++++++++ .../geometry/shape/DistanceApproximation.java | 118 +++++++++ .../spatial/geometry/shape/Ellipse.java | 233 ++++++++++++++++ .../spatial/geometry/shape/Geometry2D.java | 57 ++++ .../spatial/geometry/shape/IntersectCase.java | 25 ++ .../lucene/spatial/geometry/shape/LLRect.java | 105 ++++++++ .../spatial/geometry/shape/LineSegment.java | 81 ++++++ .../spatial/geometry/shape/Point2D.java | 116 ++++++++ .../spatial/geometry/shape/Rectangle.java | 103 ++++++++ .../spatial/geometry/shape/Vector2D.java | 120 +++++++++ .../spatial/tier/BoundaryBoxFilter.java | 210 +++++++++++++++ .../spatial/tier/CartesianPolyFilter.java | 106 ++++++++ .../spatial/tier/CartesianShapeFilter.java | 75 ++++++ .../lucene/spatial/tier/DistanceFilter.java | 248 ++++++++++++++++++ .../lucene/spatial/tier/DistanceHandler.java | 100 +++++++ .../lucene/spatial/tier/DistanceQuery.java | 104 ++++++++ .../spatial/tier/DistanceSortSource.java | 93 +++++++ .../lucene/spatial/tier/DistanceUtils.java | 63 +++++ .../spatial/tier/InvalidGeoException.java | 30 +++ .../org/apache/lucene/spatial/tier/Shape.java | 51 ++++ .../projections/CartesianTierPlotter.java | 170 ++++++++++++ .../spatial/tier/projections/IProjector.java | 23 ++ .../tier/projections/SinusoidalProjector.java | 41 +++ .../lucene/spatial/tier/DistanceCheck.java | 51 ++++ .../apache/lucene/spatial/tier/PolyShape.java | 1 + .../lucene/spatial/tier/TestCartesian.java | 225 ++++++++++++++++ .../lucene/spatial/tier/TestDistance.java | 137 ++++++++++ 36 files changed, 3563 insertions(+) create mode 100644 contrib/spatial/build.xml create mode 100644 contrib/spatial/pom.xml.template create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/ISerialChainFilter.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/NumberUtils.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/SerialChainFilter.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/CartesianPoint.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FixedLatLng.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FloatLatLng.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/LatLng.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/DistanceApproximation.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Ellipse.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Geometry2D.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/IntersectCase.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LLRect.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LineSegment.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Point2D.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Rectangle.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Vector2D.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/tier/BoundaryBoxFilter.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianPolyFilter.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianShapeFilter.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFilter.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceHandler.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceQuery.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceSortSource.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceUtils.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/tier/InvalidGeoException.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/tier/Shape.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/CartesianTierPlotter.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/IProjector.java create mode 100644 contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/SinusoidalProjector.java create mode 100644 contrib/spatial/src/test/org/apache/lucene/spatial/tier/DistanceCheck.java create mode 100644 contrib/spatial/src/test/org/apache/lucene/spatial/tier/PolyShape.java create mode 100644 contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java create mode 100644 contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestDistance.java diff --git a/contrib/spatial/build.xml b/contrib/spatial/build.xml new file mode 100644 index 00000000000..261e47954f8 --- /dev/null +++ b/contrib/spatial/build.xml @@ -0,0 +1,32 @@ + + + + + + + + Lucene Spatial Indexing + + + + + + + + + diff --git a/contrib/spatial/pom.xml.template b/contrib/spatial/pom.xml.template new file mode 100644 index 00000000000..40bbd5965d4 --- /dev/null +++ b/contrib/spatial/pom.xml.template @@ -0,0 +1,50 @@ + + + + + 4.0.0 + + org.apache.lucene + lucene-contrib + @version@ + + org.apache.lucene + lucene-instantiated + Lucene InstantiatedIndex + @version@ + InstantiatedIndex, alternative RAM store for small corpora. + jar + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + + + + + + diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/ISerialChainFilter.java b/contrib/spatial/src/java/org/apache/lucene/spatial/ISerialChainFilter.java new file mode 100644 index 00000000000..eb3c3b88a9d --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/ISerialChainFilter.java @@ -0,0 +1,34 @@ +/** + * 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; + +import java.io.IOException; +import java.util.BitSet; + +import org.apache.lucene.index.CorruptIndexException; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.Filter; + +/** + * Provide an optimized filter, by allowing the bitset from + * previous filters in the bitset to be used in the next part of the chain. + * + */ +public abstract class ISerialChainFilter extends Filter { + public abstract BitSet bits(IndexReader reader, BitSet bits) throws CorruptIndexException, IOException, Exception; +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/NumberUtils.java b/contrib/spatial/src/java/org/apache/lucene/spatial/NumberUtils.java new file mode 100644 index 00000000000..d256011289c --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/NumberUtils.java @@ -0,0 +1,68 @@ +/** + * 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; + +/** + * TODO -- when solr moves NumberUtils to lucene, this should be redundant + * + * This is a copy of solr's number utils with only the functions we use... + * + * @deprecated will be replaced with lucene version of solr copy... + */ +@Deprecated +public class NumberUtils { + + + public static String long2sortableStr(long val) { + char[] arr = new char[5]; + long2sortableStr(val,arr,0); + return new String(arr,0,5); + } + + public static String double2sortableStr(double val) { + long f = Double.doubleToRawLongBits(val); + if (f<0) f ^= 0x7fffffffffffffffL; + return long2sortableStr(f); + } + + public static double SortableStr2double(String val) { + long f = SortableStr2long(val,0,6); + if (f<0) f ^= 0x7fffffffffffffffL; + return Double.longBitsToDouble(f); + } + + public static int long2sortableStr(long val, char[] out, int offset) { + val += Long.MIN_VALUE; + out[offset++] = (char)(val >>>60); + out[offset++] = (char)(val >>>45 & 0x7fff); + out[offset++] = (char)(val >>>30 & 0x7fff); + out[offset++] = (char)(val >>>15 & 0x7fff); + out[offset] = (char)(val & 0x7fff); + return 5; + } + + public static long SortableStr2long(String sval, int offset, int len) { + long val = (long)(sval.charAt(offset++)) << 60; + val |= ((long)sval.charAt(offset++)) << 45; + val |= ((long)sval.charAt(offset++)) << 30; + val |= sval.charAt(offset++) << 15; + val |= sval.charAt(offset); + val -= Long.MIN_VALUE; + return val; + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/SerialChainFilter.java b/contrib/spatial/src/java/org/apache/lucene/spatial/SerialChainFilter.java new file mode 100644 index 00000000000..718e496062e --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/SerialChainFilter.java @@ -0,0 +1,199 @@ +/** + * 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; + +import java.io.IOException; +import java.util.BitSet; + +import org.apache.lucene.index.CorruptIndexException; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.Filter; + +/** + * + * Provide a serial chain filter, passing the bitset in with the + * index reader to each of the filters in an ordered fashion. + * + * Based off chain filter, but will some improvements to allow a narrowed down + * filtering. Traditional filter required iteration through an IndexReader. + * + * By implementing the ISerialChainFilter class, you can create a bits(IndexReader reader, BitSet bits) + * @see org.apache.lucene.spatial.ISerialChainFilter + * + */ +public class SerialChainFilter extends Filter { + + /** + * $Id: SerialChainFilter.java 136 2008-12-17 16:16:38Z ryantxu $ + */ + private static final long serialVersionUID = 1L; + private Filter chain[]; + public static final int SERIALAND = 1; + public static final int SERIALOR = 2; + public static final int AND = 3; // regular filters may be used first + public static final int OR = 4; // regular filters may be used first + public static final int DEFAULT = SERIALOR; + + private int actionType[]; + + public SerialChainFilter(Filter chain[]){ + this.chain = chain; + this.actionType = new int[] {DEFAULT}; + } + + public SerialChainFilter(Filter chain[], int actionType[]){ + this.chain= chain; + this.actionType = actionType; + } + + /* (non-Javadoc) + * @see org.apache.lucene.search.Filter#bits(org.apache.lucene.index.IndexReader) + */ + @Override + public BitSet bits(IndexReader reader) throws CorruptIndexException, IOException { + + BitSet bits = new BitSet(reader.maxDoc()); + int chainSize = chain.length; + int actionSize = actionType.length; + int i = 0; + + /** + * taken from ChainedFilter, first and on an empty bitset results in 0 + */ + if (actionType[i] == AND){ + try { + bits = (BitSet) chain[i].bits(reader).clone(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + ++i; + } + + for( ; i < chainSize; i++) { + + int action = (i < actionSize)? actionType[i]: DEFAULT; + + switch (action){ + + case (SERIALAND): + try { + bits.and(((ISerialChainFilter) chain[i]).bits(reader, bits)); + } catch (CorruptIndexException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + break; + case (SERIALOR): + try { + bits.or(((ISerialChainFilter) chain[i]).bits(reader,bits)); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + break; + case (AND): + bits.and(chain[i].bits(reader)); + break; + case (OR): + bits.and(chain[i].bits(reader)); + break; + } + + } + return bits; + } + + /** + * @return the chain + */ + Filter[] getChain() { + return chain; + } + + /** + * @return the actionType + */ + int[] getActionType() { + return actionType; + } + + /** + * Returns true if o is equal to this. + * + * @see org.apache.lucene.search.RangeFilter#equals + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SerialChainFilter)) return false; + SerialChainFilter other = (SerialChainFilter) o; + + if (this.chain.length != other.getChain().length || + this.actionType.length != other.getActionType().length) + return false; + + for (int i = 0; i < this.chain.length; i++) { + if (this.actionType[i] != other.getActionType()[i] || + (!this.chain[i].equals(other.getChain()[i]))) + return false; + } + return true; + } + + /** + * Returns a hash code value for this object. + * + * @see org.apache.lucene.search.RangeFilter#hashCode + */ + @Override + public int hashCode() { + if (chain.length == 0) + return 0; + + int h = chain[0].hashCode() ^ new Integer(actionType[0]).hashCode(); + for (int i = 1; i < this.chain.length; i++) { + h ^= chain[i].hashCode(); + h ^= new Integer(actionType[i]).hashCode(); + } + return h; + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("SerialChainFilter("); + for (int i = 0; i < chain.length; i++) { + switch(actionType[i]) { + case (SERIALAND): buf.append("SERIALAND"); break; + case (SERIALOR): buf.append("SERIALOR"); break; + case (AND): buf.append("AND"); break; + case (OR): buf.append("OR"); break; + default: buf.append(actionType[i]); + } + buf.append(" " + chain[i].toString() + " "); + } + return buf.toString().trim() + ")"; + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/CartesianPoint.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/CartesianPoint.java new file mode 100644 index 00000000000..44449c50a73 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/CartesianPoint.java @@ -0,0 +1,56 @@ +/** + * 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.geometry; + +/** + * Represents lat/lngs as fixed point numbers translated so that all + * world coordinates are in the first quadrant. The same fixed point + * scale as is used for FixedLatLng is employed. + */ +public class CartesianPoint { + private int x; + private int y; + + public CartesianPoint(int x, int y) { + this.x=x; + this.y=y; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + @Override + public String toString() { + return "Point(" + x + "," + y + ")"; + } + + /** + * Return a new point translated in the x and y dimensions + * @param i + * @param translation + * @return + */ + public CartesianPoint translate(int deltaX, int deltaY) { + return new CartesianPoint(this.x+deltaX, this.y+deltaY); + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java new file mode 100644 index 00000000000..454015a735c --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java @@ -0,0 +1,23 @@ +/** + * 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.geometry; + +public enum DistanceUnits { + MILES, + KILOMETERS; +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FixedLatLng.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FixedLatLng.java new file mode 100644 index 00000000000..5be90afa27e --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FixedLatLng.java @@ -0,0 +1,135 @@ +/** + * 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.geometry; + +public class FixedLatLng extends LatLng { + public static final double SCALE_FACTOR=1000000; + public static final int SCALE_FACTOR_INT=1000000; + + private int lat, lng; + private boolean normalized; + + public FixedLatLng(int lat, int lng) { + setLat(lat); + setLng(lng); + } + + public FixedLatLng(LatLng ll) { + this.lat=ll.getFixedLat(); + this.lng=ll.getFixedLng(); + } + + protected void setLat(int lat) { + if (lat>90*SCALE_FACTOR || lat<-90*SCALE_FACTOR) { + throw new IllegalArgumentException("Illegal lattitude"); + } + this.lat=lat; + } + + protected void setLng(int lng) { + this.lng=lng; + } + + @Override + public boolean equals(LatLng other) { + return lat==other.getFixedLat() && lng==other.getFixedLng(); + } + + public static double fixedToDouble(int fixed) { + return (fixed)/SCALE_FACTOR; + } + + public static int doubleToFixed(double d) { + return (int)(d*SCALE_FACTOR); + } + + @Override + public LatLng copy() { + return new FixedLatLng(this); + } + + @Override + public int getFixedLat() { + return lat; + } + + @Override + public int getFixedLng() { + return lng; + } + + @Override + public double getLat() { + return fixedToDouble(lat); + } + + @Override + public double getLng() { + return fixedToDouble(lng); + } + + @Override + public boolean isFixedPoint() { + return true; + } + + @Override + public FixedLatLng toFixed() { + return this; + } + + @Override + public FloatLatLng toFloat() { + return new FloatLatLng(this); + } + + @Override + public boolean isNormalized() { + return + normalized || ( + (lng>=-180*SCALE_FACTOR_INT) && + (lng<=180*SCALE_FACTOR_INT) + ); + } + + @Override + public LatLng normalize() { + if (isNormalized()) return this; + + int delta=0; + if (lng<0) delta=360*SCALE_FACTOR_INT; + if (lng>=0) delta=-360*SCALE_FACTOR_INT; + + int newLng=lng; + while (newLng<=-180*SCALE_FACTOR_INT || newLng>=180*SCALE_FACTOR_INT) { + newLng+=delta; + } + + FixedLatLng ret=new FixedLatLng(lat, newLng); + ret.normalized=true; + return ret; + } + + @Override + public LatLng calculateMidpoint(LatLng other) { + return new FixedLatLng( + (lat+other.getFixedLat())/2, + (lng+other.getFixedLng())/2); + } + +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FloatLatLng.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FloatLatLng.java new file mode 100644 index 00000000000..01f0801f8db --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/FloatLatLng.java @@ -0,0 +1,116 @@ +/** + * 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.geometry; + +public class FloatLatLng extends LatLng { + private double lat; + private double lng; + private boolean normalized; + + public FloatLatLng(double lat, double lng) { + if (lat>90.0 || lat<-90.0) throw new IllegalArgumentException("Illegal lattitude value " + lat); + this.lat=lat; + this.lng=lng; + } + + public FloatLatLng(LatLng ll) { + this.lat=ll.getLat(); + this.lng=ll.getLng(); + } + + @Override + public boolean equals(LatLng other) { + return lat==other.getLat() && lng==other.getLng(); + } + + + @Override + public LatLng copy() { + return new FloatLatLng(this); + } + + @Override + public int getFixedLat() { + return FixedLatLng.doubleToFixed(this.lat); + } + + @Override + public int getFixedLng() { + return FixedLatLng.doubleToFixed(this.lng); + } + + @Override + public double getLat() { + return this.lat; + } + + @Override + public double getLng() { + return this.lng; + } + + @Override + public boolean isFixedPoint() { + return false; + } + + @Override + public FixedLatLng toFixed() { + return new FixedLatLng(this); + } + + @Override + public FloatLatLng toFloat() { + return this; + } + + @Override + public boolean isNormalized() { + return + normalized || ( + (lng>=-180) && + (lng<=180) + ); + } + + @Override + public LatLng normalize() { + if (isNormalized()) return this; + + double delta=0; + if (lng<0) delta=360; + if (lng>=0) delta=-360; + + double newLng=lng; + while (newLng<=-180 || newLng>=180) { + newLng+=delta; + } + + FloatLatLng ret=new FloatLatLng(lat, newLng); + ret.normalized=true; + return ret; + } + + @Override + public LatLng calculateMidpoint(LatLng other) { + return new FloatLatLng( + (lat+other.getLat())/2.0, + (lng+other.getLng())/2.0); + } + +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/LatLng.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/LatLng.java new file mode 100644 index 00000000000..5e8efc5a569 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/LatLng.java @@ -0,0 +1,164 @@ +/** + * 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.geometry; + + +/** + * Abstract base lat-lng class which can manipulate fixed point or floating + * point based coordinates. Instances are immutable. + * + * @see FixedLatLngTest + * @see FloatLatLng + * + */ +public abstract class LatLng { + + public abstract boolean isNormalized(); + + public abstract boolean isFixedPoint(); + + public abstract LatLng normalize(); + + public abstract int getFixedLat(); + + public abstract int getFixedLng(); + + public abstract double getLat(); + + public abstract double getLng(); + + public abstract LatLng copy(); + + public abstract FixedLatLng toFixed(); + + public abstract FloatLatLng toFloat(); + + public abstract boolean equals(LatLng other); + + @Override + public boolean equals(Object other) { + if (!(other instanceof LatLng)) return false; + return equals((LatLng)other); + } + + /** + * Convert the lat/lng into the cartesian coordinate plane such that all + * world coordinates are represented in the first quadrant. + * The x dimension corresponds to latitude and y corresponds to longitude. + * The translation starts with the normalized latlng and adds 180 to the latitude and + * 90 to the longitude (subject to fixed point scaling). + * + * @return + */ + public CartesianPoint toCartesian() { + LatLng ll=normalize(); + + int lat=ll.getFixedLat(); + int lng=ll.getFixedLng(); + + return new CartesianPoint( + lng+180*FixedLatLng.SCALE_FACTOR_INT, + lat+90*FixedLatLng.SCALE_FACTOR_INT + ); + } + + /** + * The inverse of toCartesian(). Always returns a FixedLatLng. + * @param pt + * @return + */ + public static LatLng fromCartesian(CartesianPoint pt) { + int lat=pt.getY() - 90 * FixedLatLng.SCALE_FACTOR_INT; + int lng=pt.getX() - 180 * FixedLatLng.SCALE_FACTOR_INT; + + return new FixedLatLng(lat, lng); + } + + /** + * Calculates the distance between two lat/lng's in miles. + * Imported from mq java client. + * + * @param ll2 + * Second lat,lng position to calculate distance to. + * + * @return Returns the distance in miles. + */ + public double arcDistance(LatLng ll2) { + return arcDistance(ll2, DistanceUnits.MILES); + } + + /** + * Calculates the distance between two lat/lng's in miles or meters. + * Imported from mq java client. Variable references changed to match. + * + * @param ll2 + * Second lat,lng position to calculate distance to. + * @param lUnits + * Units to calculate distace, defaults to miles + * + * @return Returns the distance in meters or miles. + */ + public double arcDistance(LatLng ll2, DistanceUnits lUnits) { + LatLng ll1 = normalize(); + ll2 = ll2.normalize(); + + double lat1 = ll1.getLat(), lng1 = ll1.getLng(); + double lat2 = ll2.getLat(), lng2 = ll2.getLng(); + + // Check for same position + if (lat1 == lat2 && lng1 == lng2) + return 0.0; + + // Get the m_dLongitude diffeernce. Don't need to worry about + // crossing 180 since cos(x) = cos(-x) + double dLon = lng2 - lng1; + + double a = radians(90.0 - lat1); + double c = radians(90.0 - lat2); + double cosB = (Math.cos(a) * Math.cos(c)) + + (Math.sin(a) * Math.sin(c) * Math.cos(radians(dLon))); + + double radius = (lUnits == DistanceUnits.MILES) ? 3963.205/* MILERADIUSOFEARTH */ + : 6378.160187/* KMRADIUSOFEARTH */; + + // Find angle subtended (with some bounds checking) in radians and + // multiply by earth radius to find the arc distance + if (cosB < -1.0) + return 3.14159265358979323846/* PI */* radius; + else if (cosB >= 1.0) + return 0; + else + return Math.acos(cosB) * radius; + } + + private double radians(double a) { + return a * 0.01745329251994; + } + + @Override + public String toString() { + return "[" + getLat() + "," + getLng() + "]"; + } + + /** + * Calculate the midpoint between this point an another. Respects fixed vs floating point + * @param other + * @return + */ + public abstract LatLng calculateMidpoint(LatLng other); +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/DistanceApproximation.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/DistanceApproximation.java new file mode 100644 index 00000000000..fc053c22d04 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/DistanceApproximation.java @@ -0,0 +1,118 @@ +/** + * 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.geometry.shape; + +/** + * Imported from mq java client. No changes made. + */ +public class DistanceApproximation +{ + private double m_testLat; + private double m_testLng; + private double m_mpd; + private static final double m_milesPerLngDeg[]={ + 69.170976f, 69.160441f, 69.128838f, 69.076177f, 69.002475f, + 68.907753f, 68.792041f, 68.655373f, 68.497792f, 68.319345f, + 68.120088f, 67.900079f, 67.659387f, 67.398085f, 67.116253f, + 66.813976f, 66.491346f, 66.148462f, 65.785428f, 65.402355f, + 64.999359f, 64.576564f, 64.134098f, 63.672096f, 63.190698f, + 62.690052f, 62.170310f, 61.631630f, 61.074176f, 60.498118f, + 59.903632f, 59.290899f, 58.660106f, 58.011443f, 57.345111f, + 56.661310f, 55.960250f, 55.242144f, 54.507211f, 53.755675f, + 52.987764f, 52.203713f, 51.403761f, 50.588151f, 49.757131f, + 48.910956f, 48.049882f, 47.174172f, 46.284093f, 45.379915f, + 44.461915f, 43.530372f, 42.585570f, 41.627796f, 40.657342f, + 39.674504f, 38.679582f, 37.672877f, 36.654698f, 35.625354f, + 34.585159f, 33.534429f, 32.473485f, 31.402650f, 30.322249f, + 29.232613f, 28.134073f, 27.026963f, 25.911621f, 24.788387f, + 23.657602f, 22.519612f, 21.374762f, 20.223401f, 19.065881f, + 17.902554f, 16.733774f, 15.559897f, 14.381280f, 13.198283f, + 12.011266f, 10.820591f, 9.626619f, 8.429716f, 7.230245f, + 6.028572f, 4.825062f, 3.620083f, 2.414002f, 1.207185f, + 1.000000f}; + + public static final double MILES_PER_LATITUDE = 69.170976f; + public static final double KILOMETERS_PER_MILE = 1.609347f; + + + public DistanceApproximation() + { + } + + public void setTestPoint(double lat, double lng) + { + m_testLat = lat; + m_testLng = lng; + m_mpd = m_milesPerLngDeg[(int)(Math.abs(lat) + 0.5f)]; + } + + // Approximate arc distance between 2 lat,lng positions using miles per + // latitude and longitude degree + public double getDistanceSq(double lat, double lng) + { + double latMiles = (lat - m_testLat) * MILES_PER_LATITUDE; + + // Approximate longitude miles using the miles per degree assuming the + // middle latitude/longitude. This is less accurate at high (near + // polar) latitudes but no road network is present at the poles! + // If we ever have any roads crossing the international date we will + // have to worry about that case. + double lngMiles = (lng - m_testLng) * m_mpd; + + // Find the squared distance by the Pythagorean theorem (without sqrt) + return (latMiles * latMiles + lngMiles * lngMiles); + } + + // Approximate arc distance between a segment (with lat,lng endpoints) and + // the test position + public double getDistanceSq(double lat1, double lng1, double lat2, double lng2) + { + // Check if lat1,lng1 is closest point. Construct a vector from point1 + // to point2 (v1) and another from point 1 to the test point (v2). + // If dot product is negative then point 1 is the closest point + double v1y = lat2 - lat1; + double v1x = lng2 - lng1; + double v2y = m_testLat - lat1; + double v2x = m_testLng - lng1; + double dot = v1x * v2x + v1y * v2y; + if (dot <= 0.0f) + return getDistanceSq(lat1, lng1); + + // Get the component of vector v2 along v1. If component is greater + // than 1 then the endpoint is the closest point. + double c = dot / (v1x * v1x + v1y * v1y); + if (c >= 1.0f) + return getDistanceSq(lat2, lng2); + + // Since we are working io lat,lng space we need to find the point + // along p1->p2 such that q->pt is perpendicular to p1->p2. We + // then find the distance squared between Q and pt. + return getDistanceSq((lat1 + v1y * c), (lng1 + v1x * c)); + } + + // Return the number of miles per degree of longitude + public static double getMilesPerLngDeg(double lat) + { + return (Math.abs(lat) <= 90.0) ? m_milesPerLngDeg[(int)(Math.abs(lat) + 0.5f)] : 69.170976f; + } + + public static double getMilesPerLatDeg() { + return MILES_PER_LATITUDE; + } +} + diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Ellipse.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Ellipse.java new file mode 100644 index 00000000000..1f9b916cb6a --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Ellipse.java @@ -0,0 +1,233 @@ +/** + * 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.geometry.shape; + + +/** + * Ellipse shape. From C++ gl. + */ +public class Ellipse implements Geometry2D { + private Point2D center; + + /** + * Half length of major axis + */ + private double a; + + /** + * Half length of minor axis + */ + private double b; + + private double k1, k2, k3; + + /** + * sin of rotation angle + */ + private double s; + + /** + * cos of rotation angle + */ + private double c; + + public Ellipse() { + center = new Point2D(0, 0); + } + + private double SQR(double d) { + return d * d; + } + + /** + * Constructor given bounding rectangle and a rotation. + * + * @param + * @param + */ + public Ellipse(Point2D p1, Point2D p2, double angle) { + center = new Point2D(); + + // Set the center + center.x((p1.x() + p2.x()) * 0.5f); + center.y((p1.y() + p2.y()) * 0.5f); + + // Find sin and cos of the angle + double angleRad = Math.toRadians(angle); + c = Math.cos(angleRad); + s = Math.sin(angleRad); + + // Find the half lengths of the semi-major and semi-minor axes + double dx = Math.abs(p2.x() - p1.x()) * 0.5; + double dy = Math.abs(p2.y() - p1.y()) * 0.5; + if (dx >= dy) { + a = dx; + b = dy; + } else { + a = dy; + b = dx; + } + + // Find k1, k2, k3 - define when a point x,y is on the ellipse + k1 = SQR(c / a) + SQR(s / b); + k2 = 2 * s * c * ((1 / SQR(a)) - (1 / SQR(b))); + k3 = SQR(s / a) + SQR(c / b); + } + + /** + * Determines if a line segment intersects the ellipse and if so finds the + * point(s) of intersection. + * + * @param seg + * Line segment to test for intersection + * @param pt0 + * OUT - intersection point (if it exists) + * @param pt1 + * OUT - second intersection point (if it exists) + * + * @return Returns the number of intersection points (0, 1, or 2). + */ + public int intersect(LineSegment seg, Point2D pt0, Point2D pt1) { + if (pt0 == null) + pt0 = new Point2D(); + if (pt1 == null) + pt1 = new Point2D(); + + // Solution is found by paramterizing the line segment and + // substituting those values into the ellipse equation. + // Results in a quadratic equation. + double x1 = center.x(); + double y1 = center.y(); + double u1 = seg.A.x(); + double v1 = seg.A.y(); + double u2 = seg.B.x(); + double v2 = seg.B.y(); + double dx = u2 - u1; + double dy = v2 - v1; + double q0 = k1 * SQR(u1 - x1) + k2 * (u1 - x1) * (v1 - y1) + k3 + * SQR(v1 - y1) - 1; + double q1 = (2 * k1 * dx * (u1 - x1)) + (k2 * dx * (v1 - y1)) + + (k2 * dy * (u1 - x1)) + (2 * k3 * dy * (v1 - y1)); + double q2 = (k1 * SQR(dx)) + (k2 * dx * dy) + (k3 * SQR(dy)); + + // Compare q1^2 to 4*q0*q2 to see how quadratic solves + double d = SQR(q1) - (4 * q0 * q2); + if (d < 0) { + // Roots are complex valued. Line containing the segment does + // not intersect the ellipse + return 0; + } + + if (d == 0) { + // One real-valued root - line is tangent to the ellipse + double t = -q1 / (2 * q2); + if (0 <= t && t <= 1) { + // Intersection occurs along line segment + pt0.x(u1 + t * dx); + pt0.y(v1 + t * dy); + return 1; + } else + return 0; + } else { + // Two distinct real-valued roots. Solve for the roots and see if + // they fall along the line segment + int n = 0; + double q = Math.sqrt(d); + double t = (-q1 - q) / (2 * q2); + if (0 <= t && t <= 1) { + // Intersection occurs along line segment + pt0.x(u1 + t * dx); + pt0.y(v1 + t * dy); + n++; + } + + // 2nd root + t = (-q1 + q) / (2 * q2); + if (0 <= t && t <= 1) { + if (n == 0) { + pt0.x(u1 + t * dx); + pt0.y(v1 + t * dy); + n++; + } else { + pt1.x(u1 + t * dx); + pt1.y(v1 + t * dy); + n++; + } + } + return n; + } + } + + public IntersectCase intersect(Rectangle r) { + // Test if all 4 corners of the rectangle are inside the ellipse + Point2D ul = new Point2D(r.MinPt().x(), r.MaxPt().y()); + Point2D ur = new Point2D(r.MaxPt().x(), r.MaxPt().y()); + Point2D ll = new Point2D(r.MinPt().x(), r.MinPt().y()); + Point2D lr = new Point2D(r.MaxPt().x(), r.MinPt().y()); + if (contains(ul) && contains(ur) && contains(ll) && contains(lr)) + return IntersectCase.CONTAINS; + + // Test if any of the rectangle edges intersect + Point2D pt0 = new Point2D(), pt1 = new Point2D(); + LineSegment bottom = new LineSegment(ll, lr); + if (intersect(bottom, pt0, pt1) > 0) + return IntersectCase.INTERSECTS; + + LineSegment top = new LineSegment(ul, ur); + if (intersect(top, pt0, pt1) > 0) + return IntersectCase.INTERSECTS; + + LineSegment left = new LineSegment(ll, ul); + if (intersect(left, pt0, pt1) > 0) + return IntersectCase.INTERSECTS; + + LineSegment right = new LineSegment(lr, ur); + if (intersect(right, pt0, pt1) > 0) + return IntersectCase.INTERSECTS; + + // Ellipse does not intersect any edge : since the case for the ellipse + // containing the rectangle was considered above then if the center + // is inside the ellipse is fully inside and if center is outside + // the ellipse is fully outside + return (r.contains(center)) ? IntersectCase.WITHIN + : IntersectCase.OUTSIDE; + } + + public double area() { + throw new UnsupportedOperationException(); + } + + public Point2D centroid() { + throw new UnsupportedOperationException(); + } + + public boolean contains(Point2D pt) { + // Plug in equation for ellipse, If evaluates to <= 0 then the + // point is in or on the ellipse. + double dx = pt.x() - center.x(); + double dy = pt.y() - center.y(); + double eq=(((k1 * SQR(dx)) + (k2 * dx * dy) + (k3 * SQR(dy)) - 1)); + + return eq<=0; + } + + public void translate(Vector2D v) { + throw new UnsupportedOperationException(); + } + +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Geometry2D.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Geometry2D.java new file mode 100644 index 00000000000..f7354ef6ebb --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Geometry2D.java @@ -0,0 +1,57 @@ +/** + * 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.geometry.shape; + + +/** + * Common set of operations available on 2d shapes. + */ +public interface Geometry2D { + /** + * Translate according to the vector + * @param v + */ + public void translate(Vector2D v); + + /** + * Does the shape contain the given point + * @param p + * @return + */ + public boolean contains(Point2D p); + + /** + * Return the area + * @return + */ + public double area(); + + /** + * Return the centroid + * @return + */ + public Point2D centroid(); + + /** + * Returns information about how this shape intersects the given rectangle + * @param r + * @return + */ + public IntersectCase intersect(Rectangle r); + +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/IntersectCase.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/IntersectCase.java new file mode 100644 index 00000000000..ccf833ff848 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/IntersectCase.java @@ -0,0 +1,25 @@ +/** + * 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.geometry.shape; + +public enum IntersectCase { + WITHIN, + CONTAINS, + OUTSIDE, + INTERSECTS; +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LLRect.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LLRect.java new file mode 100644 index 00000000000..0a3e001b519 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LLRect.java @@ -0,0 +1,105 @@ +/** + * 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.geometry.shape; + +import org.apache.lucene.spatial.geometry.FloatLatLng; +import org.apache.lucene.spatial.geometry.LatLng; + + + +/** + * Lat-long rect. Instances are mutable. + */ +public class LLRect { + private LatLng ll, ur; + + public LLRect(LatLng ll, LatLng ur) { + this.ll=ll; + this.ur=ur; + } + + public LLRect(LLRect other) { + this.ll=other.ll; + this.ur=other.ur; + } + + /** + * Return the area in units of lat-lng squared. This is a contrived unit + * that only has value when comparing to something else. + * @return + */ + public double area() { + return Math.abs((ll.getLat()-ur.getLat()) * (ll.getLng()-ur.getLng())); + } + + public LatLng getLowerLeft() { + return ll; + } + + public LatLng getUpperRight() { + return ur; + } + + @Override + public boolean equals(Object otherObj) { + if (!(otherObj instanceof LLRect)) return false; + return equals((LLRect)otherObj); + } + + public boolean equals(LLRect other) { + return getLowerLeft().equals(other.getLowerLeft()) && getUpperRight().equals(other.getUpperRight()); + } + + @Override + public String toString() { + return "{" + ll + ", " + ur + "}"; + } + + public LatLng getMidpoint() { + return ll.calculateMidpoint(ur); + } + + /** + * Approximates a box centered at the given point with the given width and height in miles. + * @param center + * @param widthMi + * @param heightMi + * @return + */ + public static LLRect createBox(LatLng center, double widthMi, double heightMi) { + double miplatdeg=DistanceApproximation.getMilesPerLngDeg(center.getLat()); + double miplngdeg=DistanceApproximation.getMilesPerLatDeg(); + + double lngDelta=(widthMi/2)/miplngdeg; + double latDelta=(heightMi/2)/miplatdeg; + + // TODO: Prob only works in northern hemisphere? + LatLng ll=new FloatLatLng(center.getLat()-latDelta, center.getLng()-lngDelta); + LatLng ur=new FloatLatLng(center.getLat()+latDelta, center.getLng()+lngDelta); + + return new LLRect(ll, ur); + } + + /** + * Returns a rectangle shape for the bounding box + * @return + */ + public Rectangle toRectangle() { + return new Rectangle(ll.getLng(), ll.getLat(), ur.getLng(), ur.getLat()); + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LineSegment.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LineSegment.java new file mode 100644 index 00000000000..e3dee2dc526 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/LineSegment.java @@ -0,0 +1,81 @@ +/** + * 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.geometry.shape; + + +/** + * 2d line segment. + */ +public class LineSegment { + public final Point2D A = new Point2D(); + public final Point2D B = new Point2D(); + + public LineSegment() { + A.set(0, 0); + B.set(0, 0); + } + + public LineSegment(Point2D p1, Point2D p2) { + A.set(p1); + B.set(p2); + } + + /** + * Finds the distance of a specified point from the line segment and the + * closest point on the segement to the specified point. + * + * @param P + * Test point. + * @param closestPt + * (Return) Closest point on the segment to c. + * + * @return Returns the distance from P to the closest point on the segment. + */ + public double distance(Point2D P, Point2D /* out */closestPt) { + if (closestPt == null) + closestPt = new Point2D(); + + // Construct vector v (AB) and w (AP) + Vector2D v = new Vector2D(A, B); + Vector2D w = new Vector2D(A, P); + + // Numerator of the component of w onto v. If <= 0 then A + // is the closest point. By separating into the numerator + // and denominator of the component we avoid a division unless + // it is necessary. + double n = w.dot(v); + if (n <= 0.0f) { + closestPt.set(A); + return w.norm(); + } + + // Get the denominator of the component. If the component >= 1 + // (d <= n) then point B is the closest point + double d = v.dot(v); + if (d <= n) { + closestPt.set(B); + return new Vector2D(B, P).norm(); + } + + // Closest point is along the segment. The point is the projection of + // w onto v. + closestPt.set(v.mult(n / d)); + closestPt.add(A); + return new Vector2D(closestPt, P).norm(); + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Point2D.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Point2D.java new file mode 100644 index 00000000000..9b72f9f80b0 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Point2D.java @@ -0,0 +1,116 @@ +/** + * 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.geometry.shape; + + +/** + * Point class. This type is mutable. + */ +public class Point2D { + private double x; + private double y; + + public Point2D(double x, double y) { + this.x=x; + this.y=y; + } + + public Point2D() { + this.x=0; + this.y=0; + } + + public Point2D(Point2D other) { + this.x=other.x; + this.y=other.y; + } + + @Override + public String toString() { + return "(" + x + "," + y + ")"; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double x() { + return x; + } + + public double y() { + return y; + } + + public void x(double x) { + this.x=x; + } + + public void y(double y) { + this.y=y; + } + + public void setX(double x) { + this.x = x; + } + + public void setY(double y) { + this.y = y; + } + + public void set(double x, double y) { + this.x=x; + this.y=y; + } + + public boolean equals(Point2D other) { + return other!=null && x==other.x && y==other.y; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Point2D)) return false; + return equals((Point2D)other); + } + + public void add(Vector2D v) { + this.x+=v.getX(); + this.y+=v.getY(); + } + + public void set(Point2D p1) { + this.x=p1.getX(); + this.y=p1.getY(); + } + + public void add(Point2D a) { + this.x+=a.getX(); + this.y+=a.getY(); + } + + public void set(Vector2D v) { + this.x=v.getX(); + this.y=v.getY(); + } + + +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Rectangle.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Rectangle.java new file mode 100644 index 00000000000..6a6c2aaabad --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Rectangle.java @@ -0,0 +1,103 @@ +/** + * 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.geometry.shape; + + +/** + * Rectangle shape. + */ +public class Rectangle implements Geometry2D { + private Point2D ptMin, ptMax; + + public Rectangle() { + ptMin=new Point2D(-1, 1); + ptMax=new Point2D(1, 1); + } + + public Rectangle(Point2D ptMin, Point2D ptMax) { + this.ptMin=new Point2D(ptMin); + this.ptMax=new Point2D(ptMax); + } + + public Rectangle(double x1, double y1, double x2, double y2) { + set(x1, y1, x2, y2); + } + + @Override + public String toString() { + return "[" + ptMin + "," + ptMax + "]"; + } + + private void set(double x1, double y1, double x2, double y2) { + this.ptMin=new Point2D(Math.min(x1, x2), Math.min(y1, y2)); + this.ptMax=new Point2D(Math.max(x1, x2), Math.max(y1, y2)); + } + + public boolean equals(Rectangle other) { + return other.ptMin.equals(ptMin) && other.ptMax.equals(ptMax); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Rectangle)) return false; + return equals((Rectangle)other); + } + + public double area() { + return (ptMax.getX() - ptMin.getX()) * (ptMax.getY() - ptMin.getY()); + } + + public Point2D centroid() { + return new Point2D( (ptMin.getX() + ptMax.getX()) / 2, + (ptMin.getY() + ptMax.getY()) / 2); + } + + public boolean contains(Point2D p) { + return p.getX() >= ptMin.getX() && + p.getX() <= ptMax.getX() && + p.getY() >= ptMin.getY() && + p.getY() <= ptMax.getY(); + } + + public void translate(Vector2D v) { + ptMin.add(v); + ptMax.add(v); + } + + Point2D MinPt() { + return ptMin; + } + + Point2D MaxPt() { + return ptMax; + } + + public IntersectCase intersect(Rectangle r) { + throw new UnsupportedOperationException(); + // TODO + } + + public Point2D getMaxPoint() { + return ptMax; + } + + public Point2D getMinPoint() { + return ptMin; + } + +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Vector2D.java b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Vector2D.java new file mode 100644 index 00000000000..1666a3216ae --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/shape/Vector2D.java @@ -0,0 +1,120 @@ +/** + * 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.geometry.shape; + + +/** + * 2D vector + */ +public class Vector2D { + private double x; + private double y; + + /** + * Create a vector from the origin of the coordinate system to the given + * point + * + * @param x + * @param y + */ + public Vector2D(double x, double y) { + this.x = x; + this.y = y; + } + + /** + * Create a vector from the origin of the coordinate system to the given + * point + */ + public Vector2D(Point2D p) { + this(p.getX(), p.getY()); + } + + /** + * Create a vector from one point to another + * + * @param from + * @param to + */ + public Vector2D(Point2D from, Point2D to) { + this(to.getX() - from.getX(), to.getY() - from.getY()); + } + + public Vector2D() { + this.x = 0; + this.y = 0; + } + + public Vector2D(Vector2D other) { + this.x = other.x; + this.y = other.y; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public void setX(double x) { + this.x = x; + } + + public void setY(double y) { + this.y = y; + } + + public void set(double x, double y) { + this.x = x; + this.y = y; + } + + public boolean equals(Vector2D other) { + return other != null && x == other.x && y == other.y; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Vector2D)) + return false; + return equals((Vector2D) other); + } + + public double dot(Vector2D in) { + return ((x) * in.x) + (y * in.y); + } + + /** + * Vector length (magnitude) squared + */ + public double normSqr() { + // Cast to F to prevent overflows + return (x * x) + (y * y); + } + + public double norm() { + return Math.sqrt(normSqr()); + } + + public Vector2D mult(double d) { + return new Vector2D(x*d, y*d); + } + +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/tier/BoundaryBoxFilter.java b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/BoundaryBoxFilter.java new file mode 100644 index 00000000000..f28d925cba5 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/BoundaryBoxFilter.java @@ -0,0 +1,210 @@ +/** + * 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.io.IOException; +import java.util.BitSet; +import java.util.logging.Logger; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.Term; +import org.apache.lucene.index.TermDocs; +import org.apache.lucene.index.TermEnum; +import org.apache.lucene.search.Filter; +import org.apache.lucene.spatial.NumberUtils; + + + +/** + * An implementation of org.apache.lucene.search.RangeFilter that + * caches values extracted from the index. + * + */ +public class BoundaryBoxFilter extends Filter { + + private static final long serialVersionUID = 1L; + private String fieldName; + private String lowerTerm; + private String upperTerm; + private boolean includeLower; + private boolean includeUpper; + private Logger log = Logger.getLogger(getClass().getName()); + + // cache of values extracted from the index + // TODO: add generics + //private Map coords; + + /** + * @param fieldName The field this range applies to + * @param lowerTerm The lower bound on this range + * @param upperTerm The upper bound on this range + * @param includeLower Does this range include the lower bound? + * @param includeUpper Does this range include the upper bound? + * @throws IllegalArgumentException if both terms are null or if + * lowerTerm is null and includeLower is true (similar for upperTerm + * and includeUpper) + */ + public BoundaryBoxFilter(String fieldName, String lowerTerm, String upperTerm, + boolean includeLower, boolean includeUpper) { + this.fieldName = fieldName; + this.lowerTerm = lowerTerm; + this.upperTerm = upperTerm; + this.includeLower = includeLower; + this.includeUpper = includeUpper; + + if (null == lowerTerm && null == upperTerm) { + throw new IllegalArgumentException + ("At least one value must be non-null"); + } + if (includeLower && null == lowerTerm) { + throw new IllegalArgumentException + ("The lower bound must be non-null to be inclusive"); + } + if (includeUpper && null == upperTerm) { + throw new IllegalArgumentException + ("The upper bound must be non-null to be inclusive"); + } + } + + + /** + * Returns a BitSet with true for documents which should be + * permitted in search results, and false for those that should + * not. + */ + @Override + public BitSet bits(IndexReader reader) throws IOException { + long start = System.currentTimeMillis(); + + BitSet bits = new BitSet(reader.maxDoc()); + TermEnum enumerator = + (null != lowerTerm + ? reader.terms(new Term(fieldName, lowerTerm)) + : reader.terms(new Term(fieldName,""))); + + //coords = new HashMap(enumerator.docFreq()); + + try { + if (enumerator.term() == null) { + return bits; + } + + boolean checkLower = false; + if (!includeLower) // make adjustments to set to exclusive + checkLower = true; + + TermDocs termDocs = reader.termDocs(); + try { + do { + Term term = enumerator.term(); + if (term != null && term.field().equals(fieldName)) { + if (!checkLower || null==lowerTerm || term.text().compareTo(lowerTerm) > 0) { + checkLower = false; + if (upperTerm != null) { + int compare = upperTerm.compareTo(term.text()); + // if beyond the upper term, or is exclusive and + // this is equal to the upper term, break out + if ((compare < 0) || + (!includeUpper && compare==0)) { + break; + } + } + // we have a good term, find the docs + termDocs.seek(enumerator.term()); + while (termDocs.next()) { + bits.set(termDocs.doc()); + } + } + } + else { + break; + } + } + while (enumerator.next()); + } + finally { + termDocs.close(); + } + } + finally { + enumerator.close(); + } + + long end = System.currentTimeMillis(); + log.info("BoundaryBox Time Taken: "+ (end - start)); + return bits; + } + + @Override + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append(fieldName); + buffer.append(":"); + buffer.append(includeLower ? "[" : "{"); + if (null != lowerTerm) { + buffer.append(NumberUtils.SortableStr2double(lowerTerm)); + } + buffer.append("-"); + if (null != upperTerm) { + buffer.append(NumberUtils.SortableStr2double(upperTerm)); + } + buffer.append(includeUpper ? "]" : "}"); + return buffer.toString(); + } + + public String getFieldName() { + return fieldName; + } + + + /** + * Returns true if o is equal to this. + * + * @see org.apache.lucene.search.RangeFilter#equals + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BoundaryBoxFilter)) return false; + BoundaryBoxFilter other = (BoundaryBoxFilter) o; + + if (!this.fieldName.equals(other.fieldName) + || this.includeLower != other.includeLower + || this.includeUpper != other.includeUpper + ) { return false; } + if (this.lowerTerm != null ? !this.lowerTerm.equals(other.lowerTerm) : other.lowerTerm != null) return false; + if (this.upperTerm != null ? !this.upperTerm.equals(other.upperTerm) : other.upperTerm != null) return false; + return true; + } + + /** + * Returns a hash code value for this object. + * + * @see org.apache.lucene.search.RangeFilter#hashCode + */ + @Override + public int hashCode() { + int h = fieldName.hashCode(); + h ^= lowerTerm != null ? lowerTerm.hashCode() : 0xB6ECE882; + h = (h << 1) | (h >>> 31); // rotate to distinguish lower from upper + h ^= (upperTerm != null ? (upperTerm.hashCode()) : 0x91BEC2C2); + h ^= (includeLower ? 0xD484B933 : 0) + ^ (includeUpper ? 0x6AE423AC : 0); + return h; + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianPolyFilter.java b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianPolyFilter.java new file mode 100644 index 00000000000..8fc4086e9d4 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianPolyFilter.java @@ -0,0 +1,106 @@ +/** + * 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 java.util.logging.Logger; + +import org.apache.lucene.search.Filter; +import org.apache.lucene.spatial.geometry.shape.Rectangle; +import org.apache.lucene.spatial.tier.projections.CartesianTierPlotter; +import org.apache.lucene.spatial.tier.projections.IProjector; +import org.apache.lucene.spatial.tier.projections.SinusoidalProjector; + + +/** + * + */ +public class CartesianPolyFilter { + + private IProjector projector = new SinusoidalProjector(); + private Logger log = Logger.getLogger(getClass().getName()); + + public Shape getBoxShape(double latitude, double longitude, int miles) + { + Rectangle box = DistanceUtils.getInstance().getBoundary(latitude, longitude, miles); + + double latY = box.getMaxPoint().getY();//box.getY(); + double latX = box.getMinPoint().getY() ; //box.getMaxY(); + + double longY = box.getMaxPoint().getX(); ///box.getX(); + double longX = box.getMinPoint().getX();//box.getMaxX(); + + CartesianTierPlotter ctp = new CartesianTierPlotter(2, projector); + int bestFit = ctp.bestFit(miles); + + log.info("Best Fit is : " + bestFit); + ctp = new CartesianTierPlotter(bestFit, projector); + Shape shape = new Shape(ctp.getTierFieldName()); + + // generate shape + // iterate from startX->endX + // iterate from startY -> endY + // shape.add(currentLat.currentLong); + + + double beginAt = ctp.getTierBoxId(latX, longX); + double endAt = ctp.getTierBoxId(latY, longY); + + double tierVert = ctp.getTierVerticalPosDivider(); + log.fine(" | "+ 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(); + log.fine("scale "+scale+" startX "+ startX + " endX "+endX +" startY "+ startY + " endY "+ endY +" tierVert "+ tierVert); + + double xInc = 1.0d / tierVert; + xInc = new BigDecimal(xInc).setScale(scale, RoundingMode.HALF_EVEN).doubleValue(); + + for (; startX <= endX; startX++){ + + double itY = startY; + while (itY <= endY){ + //create a boxId + // startX.startY + double boxId = startX + itY ; + shape.addBox(boxId); + //System.out.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, int miles) + { + Shape shape = getBoxShape(latitude, longitude, miles); + return new CartesianShapeFilter(shape, shape.getTierId()); + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianShapeFilter.java b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianShapeFilter.java new file mode 100644 index 00000000000..11f049b7f7b --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/CartesianShapeFilter.java @@ -0,0 +1,75 @@ +/** + * 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.io.IOException; +import java.util.BitSet; +import java.util.List; +import java.util.logging.Logger; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.Term; +import org.apache.lucene.index.TermDocs; +import org.apache.lucene.search.Filter; +import org.apache.lucene.spatial.NumberUtils; + +public class CartesianShapeFilter extends Filter { + + /** + * + */ + private static final long serialVersionUID = 1L; + private Shape shape; + private Logger log = Logger.getLogger(getClass().getName()); + private String fieldName; + + CartesianShapeFilter(Shape shape, String fieldName){ + this.shape = shape; + this.fieldName = fieldName; + } + + @Override + public BitSet bits(IndexReader reader) throws IOException { + long start = System.currentTimeMillis(); + + BitSet bits = new BitSet(reader.maxDoc()); + + TermDocs termDocs = reader.termDocs(); + List area = shape.getArea(); + int sz = area.size(); + log.info("Area size "+ sz); + + // iterate through each boxid + for (int i =0; i< sz; i++) { + double boxId = area.get(i).doubleValue(); + + termDocs.seek(new Term(fieldName, + NumberUtils.double2sortableStr(boxId))); + + // iterate through all documents + // which have this boxId + while (termDocs.next()) { + bits.set(termDocs.doc()); + } + } + + long end = System.currentTimeMillis(); + log.info("BoundaryBox Time Taken: "+ (end - start) + " found: "+bits.cardinality()+" candidates"); + return bits; + } + +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFilter.java b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFilter.java new file mode 100644 index 00000000000..14e580d43d7 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceFilter.java @@ -0,0 +1,248 @@ +/** + * 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.io.IOException; +import java.util.BitSet; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.logging.Logger; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.FieldCache; +import org.apache.lucene.spatial.ISerialChainFilter; +import org.apache.lucene.spatial.tier.DistanceHandler.Precision; + +import org.apache.lucene.spatial.NumberUtils; + + + +public class DistanceFilter extends ISerialChainFilter { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private double distance; + private double lat; + private double lng; + private String latField; + private String lngField; + private Logger log = Logger.getLogger(getClass().getName()); + + private Map distances = null; + private Precision precise = null; + + /** + * Provide a distance filter based from a center point with a radius + * in miles + * @param lat + * @param lng + * @param miles + * @param latField + * @param lngField + */ + public DistanceFilter(double lat, double lng, double miles, String latField, String lngField){ + distance = miles; + this.lat = lat; + this.lng = lng; + this.latField = latField; + this.lngField = lngField; + } + + + public Map getDistances(){ + return distances; + } + + public Double getDistance(int docid){ + return distances.get(docid); + } + + @Override + public BitSet bits(IndexReader reader) throws IOException { + + /* Create a BitSet to store the result */ + int maxdocs = reader.numDocs(); + BitSet bits = new BitSet(maxdocs); + + setPrecision(maxdocs); + // create an intermediate cache to avoid recomputing + // distances for the same point + // TODO: Why is this a WeakHashMap? + WeakHashMap cdistance = new WeakHashMap(maxdocs); + + String[] latIndex = FieldCache.DEFAULT.getStrings(reader, latField); + String[] lngIndex = FieldCache.DEFAULT.getStrings(reader, lngField); + + /* store calculated distances for reuse by other components */ + distances = new HashMap(maxdocs); + for (int i = 0 ; i < maxdocs; i++) { + + String sx = latIndex[i]; + String sy = lngIndex[i]; + + double x = NumberUtils.SortableStr2double(sx); + double y = NumberUtils.SortableStr2double(sy); + + // round off lat / longs if necessary +// x = DistanceHandler.getPrecision(x, precise); +// y = DistanceHandler.getPrecision(y, precise); + + String ck = new Double(x).toString()+","+new Double(y).toString(); + Double cachedDistance = cdistance.get(ck); + + + double d; + + if(cachedDistance != null){ + d = cachedDistance.doubleValue(); + } else { + d = DistanceUtils.getInstance().getDistanceMi(lat, lng, x, y); + cdistance.put(ck, d); + } + distances.put(i, d); + + if (d < distance){ + bits.set(i); + } + + } + + return bits; + } + + + @Override + public BitSet bits(IndexReader reader, BitSet bits) throws Exception { + + + /* Create a BitSet to store the result */ + int size = bits.cardinality(); + BitSet result = new BitSet(size); + + + /* create an intermediate cache to avoid recomputing + distances for the same point */ + HashMap cdistance = new HashMap(size); + + + /* store calculated distances for reuse by other components */ + distances = new HashMap(size); + + long start = System.currentTimeMillis(); + String[] latIndex = FieldCache.DEFAULT.getStrings(reader, latField); + String[] lngIndex = FieldCache.DEFAULT.getStrings(reader, lngField); + + /* loop over all set bits (hits from the boundary box filters) */ + int i = bits.nextSetBit(0); + while (i >= 0){ + double x,y; + + // if we have a completed + // filter chain, lat / lngs can be retrived from + // memory rather than document base. + + String sx = latIndex[i]; + String sy = lngIndex[i]; + x = NumberUtils.SortableStr2double(sx); + y = NumberUtils.SortableStr2double(sy); + + // round off lat / longs if necessary +// x = DistanceHandler.getPrecision(x, precise); +// y = DistanceHandler.getPrecision(y, precise); + + String ck = new Double(x).toString()+","+new Double(y).toString(); + Double cachedDistance = cdistance.get(ck); + double d; + + if(cachedDistance != null){ + d = cachedDistance.doubleValue(); + + } else { + d = DistanceUtils.getInstance().getDistanceMi(lat, lng, x, y); + //d = DistanceUtils.getLLMDistance(lat, lng, x, y); + cdistance.put(ck, d); + } + + distances.put(i, d); + + if (d < distance){ + result.set(i); + } + i = bits.nextSetBit(i+1); + } + + long end = System.currentTimeMillis(); + log.fine("Time taken : "+ (end - start) + + ", results : "+ distances.size() + + ", cached : "+ cdistance.size() + + ", incoming size: "+ size); + + + cdistance = null; + + return result; + } + + /** Returns true if o is equal to this. */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DistanceFilter)) return false; + DistanceFilter other = (DistanceFilter) o; + + if (this.distance != other.distance || + this.lat != other.lat || + this.lng != other.lng || + !this.latField.equals(other.latField) || + !this.lngField.equals(other.lngField)) { + return false; + } + return true; + } + + /** Returns a hash code value for this object.*/ + @Override + public int hashCode() { + int h = new Double(distance).hashCode(); + h ^= new Double(lat).hashCode(); + h ^= new Double(lng).hashCode(); + h ^= latField.hashCode(); + h ^= lngField.hashCode(); + return h; + } + + private void setPrecision(int maxDocs) { + precise = Precision.EXACT; + + if (maxDocs > 1000 && distance > 10) { + precise = Precision.TWENTYFEET; + } + + if (maxDocs > 10000 && distance > 10){ + precise = Precision.TWOHUNDREDFEET; + } + } + + public void setDistances(Map distances) { + this.distances = distances; + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceHandler.java b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceHandler.java new file mode 100644 index 00000000000..4e6165fb78a --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceHandler.java @@ -0,0 +1,100 @@ +/** + * 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.util.HashMap; +import java.util.Map; + +/** + * Provide a high level access point to distances + * Used by DistanceSortSource and DistanceQuery + * + * @author pjaol + * + */ +public class DistanceHandler { + + private Map distances; + public enum Precision {EXACT, TWOFEET, TWENTYFEET, TWOHUNDREDFEET}; + private Precision precise; + + public DistanceHandler (Map distances, Precision precise){ + this.distances = distances; + this.precise = precise; + + } + + + public static double getPrecision(double x, Precision thisPrecise){ + + if(thisPrecise != null){ + double dif = 0; + switch(thisPrecise) { + case EXACT: return x; + case TWOFEET: dif = x % 0.0001; break; + case TWENTYFEET: dif = x % 0.001; break; + case TWOHUNDREDFEET: dif = x % 0.01; break; + } + return x - dif; + } + return x; + } + + public Precision getPrecision() { + return precise; + } + + public double getDistance(int docid, double centerLat, double centerLng, double lat, double lng){ + + // check to see if we have distances + // if not calculate the distance + if(distances == null){ + return DistanceUtils.getInstance().getDistanceMi(centerLat, centerLng, lat, lng); + } + + // check to see if the doc id has a cached distance + Double docd = distances.get( docid ); + if (docd != null){ + return docd.doubleValue(); + } + + //check to see if we have a precision code + // and if another lat/long has been calculated at + // that rounded location + if (precise != null) { + double xLat = getPrecision(lat, precise); + double xLng = getPrecision(lng, precise); + + String k = new Double(xLat).toString() +","+ new Double(xLng).toString(); + + Double d = (distances.get(k)); + if (d != null){ + return d.doubleValue(); + } + } + + //all else fails calculate the distances + return DistanceUtils.getInstance().getDistanceMi(centerLat, centerLng, lat, lng); + } + + + public static void main(String args[]){ + DistanceHandler db = new DistanceHandler(new HashMap(), Precision.TWOHUNDREDFEET); + System.out.println(DistanceHandler.getPrecision(-1234.123456789, db.getPrecision())); + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceQuery.java b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceQuery.java new file mode 100644 index 00000000000..9ac0e21abd3 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceQuery.java @@ -0,0 +1,104 @@ +/** + * 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 org.apache.lucene.search.ConstantScoreQuery; +import org.apache.lucene.search.Filter; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryWrapperFilter; +import org.apache.lucene.spatial.SerialChainFilter; + + +public class DistanceQuery { + + private static final long serialVersionUID = 1L; + + public BoundaryBoxFilter latFilter; + public BoundaryBoxFilter lngFilter; + public DistanceFilter distanceFilter; + + private double lat; + private double lng; + private double miles; + private Filter cartesianFilter; + + /** + * Create a distance query using + * a boundary box wrapper around a more precise + * DistanceFilter. + * + * @see SerialChainFilter + * @param lat + * @param lng + * @param miles + */ + public DistanceQuery (double lat, double lng, double miles, String latField, String lngField, boolean needPrecise){ + + this.lat = lat; + this.lng = lng; + this.miles = miles; + + + CartesianPolyFilter cpf = new CartesianPolyFilter(); + cartesianFilter = cpf.getBoundingArea(lat, lng, (int)miles); + + /* create precise distance filter */ + if( needPrecise) + distanceFilter = new DistanceFilter(lat, lng, miles, latField, lngField); + + } + + /** + * Create a distance query using + * a boundary box wrapper around a more precise + * DistanceFilter. + * + * @see SerialChainFilter + * @param lat + * @param lng + * @param miles + */ + public Filter getFilter() { + + return new SerialChainFilter(new Filter[] {cartesianFilter, distanceFilter}, + new int[] {SerialChainFilter.AND, + SerialChainFilter.SERIALAND}); + + + } + + public Filter getFilter(Query query) { + QueryWrapperFilter qf = new QueryWrapperFilter(query); + + + return new SerialChainFilter(new Filter[] {cartesianFilter, qf, distanceFilter}, + new int[] {SerialChainFilter.AND, + SerialChainFilter.AND, + SerialChainFilter.SERIALAND}); + + } + + public Query getQuery() { + return new ConstantScoreQuery(getFilter()); + } + + @Override + public String toString() { + return "DistanceQuery lat: " + lat + " lng: " + lng + " miles: "+ miles; + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceSortSource.java b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceSortSource.java new file mode 100644 index 00000000000..6c87c4dd4d7 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceSortSource.java @@ -0,0 +1,93 @@ +/** + * 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.io.IOException; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.Filter; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.ScoreDocComparator; +import org.apache.lucene.search.SortComparatorSource; +import org.apache.lucene.search.SortField; + +public class DistanceSortSource implements SortComparatorSource { + + private static final long serialVersionUID = 1L; + + private DistanceFilter distanceFilter; + private DistanceScoreDocLookupComparator dsdlc; + + public DistanceSortSource (Filter distanceFilter){ + + this.distanceFilter = (DistanceFilter)distanceFilter; + + } + + public void cleanUp() { + distanceFilter = null; + + if (dsdlc !=null) + dsdlc.cleanUp(); + + dsdlc = null; + } + + public ScoreDocComparator newComparator(IndexReader reader, String field) throws IOException { + dsdlc = new DistanceScoreDocLookupComparator(reader, distanceFilter); + return dsdlc; + } + + + + private class DistanceScoreDocLookupComparator implements ScoreDocComparator { + + private DistanceFilter distanceFilter; + + public DistanceScoreDocLookupComparator(IndexReader reader, DistanceFilter distanceFilter) { + this.distanceFilter = distanceFilter; + return; + } + + + public int compare(ScoreDoc aDoc, ScoreDoc bDoc) { + +// if (this.distances == null) { +// distances = distanceFilter.getDistances(); +// } + double a = distanceFilter.getDistance(aDoc.doc); + double b = distanceFilter.getDistance(bDoc.doc); + if (a > b) return 1; + if (a < b )return -1; + + return 0; + } + + public int sortType() { + return SortField.DOUBLE; + } + + public Comparable sortValue(ScoreDoc iDoc) { + return distanceFilter.getDistance(iDoc.doc); + } + + public void cleanUp() { + distanceFilter = null; + } + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceUtils.java b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceUtils.java new file mode 100644 index 00000000000..60764ed9510 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceUtils.java @@ -0,0 +1,63 @@ +/** + * 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 org.apache.lucene.spatial.geometry.DistanceUnits; +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.geometry.shape.Rectangle; + + +public class DistanceUtils { + + static DistanceUtils instance = new DistanceUtils(); + + public static DistanceUtils getInstance() + { + return instance; + } + + + public double getDistanceMi(double x1, double y1, double x2, double y2) { + return getLLMDistance(x1, y1, x2, y2); + } + + /** + * + * @param x1 + * @param y1 + * @param miles + * @return boundary rectangle where getY/getX is top left, getMinY/getMinX is bottom right + */ + public Rectangle getBoundary (double x1, double y1, double miles) { + + LLRect box = LLRect.createBox( new FloatLatLng( x1, y1 ), miles, miles ); + + //System.out.println("Box: "+maxX+" | "+ maxY+" | "+ minX + " | "+ minY); + return box.toRectangle(); + + } + + public double getLLMDistance (double x1, double y1, double x2, double y2) { + + LatLng p1 = new FloatLatLng( x1, y1 ); + LatLng p2 = new FloatLatLng( x2, y2 ); + return p1.arcDistance( p2, DistanceUnits.MILES ); + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/tier/InvalidGeoException.java b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/InvalidGeoException.java new file mode 100644 index 00000000000..4920f0dc304 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/InvalidGeoException.java @@ -0,0 +1,30 @@ +/** + * 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; + +public class InvalidGeoException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public InvalidGeoException(String message){ + super(message); + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/tier/Shape.java b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/Shape.java new file mode 100644 index 00000000000..95435657b01 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/Shape.java @@ -0,0 +1,51 @@ +/** + * 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.util.ArrayList; +import java.util.List; + +/** + * @author pjaol + * + */ +public class Shape { + + private List area = new ArrayList(); + private String tierId; + + public Shape (String tierId){ + this.tierId = tierId; + } + + public void addBox(double boxId){ + area.add(boxId); + } + + public List getArea(){ + return area; + } + + public String getTierId(){ + return tierId; + } + + public boolean isInside(double boxId){ + return area.contains(boxId); + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/CartesianTierPlotter.java b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/CartesianTierPlotter.java new file mode 100644 index 00000000000..616988a440b --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/CartesianTierPlotter.java @@ -0,0 +1,170 @@ +/** + * 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; + +/** + * + */ +public class CartesianTierPlotter { + + int tierLevel; + int tierLength; + int tierBoxes; + int tierVerticalPosDivider; + IProjector projector; + final String fieldPrefix = "_localTier"; + Double idd = new Double(180); + + public CartesianTierPlotter (int tierLevel, IProjector projector) { + + this.tierLevel = tierLevel; + this.projector = projector; + + 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 = new Double(Math.ceil( + Math.log10(new Integer(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 transposded in to position + * coordinates. + * + * @param latitude + * @param longitude + * @return + */ + 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> + * @return + */ + public String getTierFieldName (){ + + return fieldPrefix + this.tierLevel; + } + + /** + * get the string name representing tierId + * _localTier<tierId> + * @param tierId + * @return + */ + 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 + * + * @param latitude + * @param longitude + * @return + */ + public int bestFit(int 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); + System.out.println("corner "+ corner); + 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 + * Math.log(value) / Math.log(2) + * @param value + * @return + */ + public double log2(double value) { + + return Math.log(value) / Math.log(2); + } +} diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/IProjector.java b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/IProjector.java new file mode 100644 index 00000000000..35b7c2fc5ae --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/IProjector.java @@ -0,0 +1,23 @@ +/** + * 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; + +public interface IProjector { + public String coordsAsString(double latitude, double longitude); + public double[] coords(double latitude, double longitude); +} \ No newline at end of file diff --git a/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/SinusoidalProjector.java b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/SinusoidalProjector.java new file mode 100644 index 00000000000..29a41c52338 --- /dev/null +++ b/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/SinusoidalProjector.java @@ -0,0 +1,41 @@ +/** + * 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; + +/** + * Based on Sinusoidal Projections + * Project a latitude / longitude on a 2D cartisian map + * + */ +public class SinusoidalProjector implements IProjector { + + + public String coordsAsString(double latitude, double longitude) { + return null; + } + + public double[] coords(double latitude, double longitude) { + double rlat = Math.toRadians(latitude); + double rlong = Math.toRadians(longitude); + double nlat = rlong * Math.cos(rlat); + double r[] = {nlat, rlong}; + return r; + + } + +} diff --git a/contrib/spatial/src/test/org/apache/lucene/spatial/tier/DistanceCheck.java b/contrib/spatial/src/test/org/apache/lucene/spatial/tier/DistanceCheck.java new file mode 100644 index 00000000000..99f1a3e61fd --- /dev/null +++ b/contrib/spatial/src/test/org/apache/lucene/spatial/tier/DistanceCheck.java @@ -0,0 +1,51 @@ +/** + * 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.text.DecimalFormat; + +import org.apache.lucene.spatial.tier.DistanceUtils; + + +public class DistanceCheck { + + /** + * @param args + */ + public static void main(String[] args) { + double lat1 = 0; + double long1 = 0; + double lat2 = 0; + double long2 = 0; + + for (int i =0; i < 90; i++){ + double dis = DistanceUtils.getInstance().getDistanceMi(lat1, long1, lat2, long2); + lat1 +=1; + lat2 = lat1 + 0.001; + + System.out.println(lat1+","+long1+","+lat2+","+long2+","+formatDistance(dis)); + + } + + } + + public static String formatDistance (Double d){ + DecimalFormat df1 = new DecimalFormat("####.000000"); + return df1.format(d); + } + +} diff --git a/contrib/spatial/src/test/org/apache/lucene/spatial/tier/PolyShape.java b/contrib/spatial/src/test/org/apache/lucene/spatial/tier/PolyShape.java new file mode 100644 index 00000000000..b65cc2c5558 --- /dev/null +++ b/contrib/spatial/src/test/org/apache/lucene/spatial/tier/PolyShape.java @@ -0,0 +1 @@ +/** * 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; /** * */ public class PolyShape { private static double lat = 38.969398; private static double lng= -77.386398; private static int miles = 1000; /** * @param args */ public static void main(String[] args) { CartesianPolyFilter cpf = new CartesianPolyFilter(); cpf.getBoxShape(lat, lng, miles); } } \ No newline at end of file diff --git a/contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java b/contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java new file mode 100644 index 00000000000..d53a7de96c6 --- /dev/null +++ b/contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java @@ -0,0 +1,225 @@ +/** + * 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.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; + +import org.apache.lucene.analysis.WhitespaceAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.Hits; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.SortField; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.spatial.tier.DistanceQuery; +import org.apache.lucene.spatial.tier.DistanceSortSource; +import org.apache.lucene.spatial.tier.DistanceUtils; +import org.apache.lucene.spatial.tier.InvalidGeoException; +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.store.Directory; +import org.apache.lucene.store.RAMDirectory; +import org.apache.lucene.spatial.NumberUtils; +import org.apache.lucene.search.function.CustomScoreQuery; +import org.apache.lucene.search.function.FieldScoreQuery; +import org.apache.lucene.search.function.FieldScoreQuery.Type; + + +/** + * + */ +public class TestCartesian extends TestCase{ + + /** + * @param args + */ + + private Directory directory; + private IndexSearcher searcher; + // reston va + private double lat = 38.969398; + private double lng= -77.386398; + private String latField = "lat"; + private String lngField = "lng"; + private List ctps = new LinkedList(); + + private IProjector project = new SinusoidalProjector(); + + + + @Override + protected void setUp() throws IOException { + directory = new RAMDirectory(); + + IndexWriter writer = new IndexWriter(directory, new WhitespaceAnalyzer(), true); + + setUpPlotter( 2, 15); + + addData(writer); + + } + + + private void setUpPlotter(int base, int top) { + + for (; base <= top; base ++){ + ctps.add(new CartesianTierPlotter(base,project )); + + } + } + + private void addPoint(IndexWriter writer, String name, double lat, double lng) throws IOException{ + + Document doc = new Document(); + + doc.add(new Field("name", name,Field.Store.YES, Field.Index.TOKENIZED)); + + // convert the lat / long to lucene fields + doc.add(new Field(latField, NumberUtils.double2sortableStr(lat),Field.Store.YES, Field.Index.UN_TOKENIZED)); + doc.add(new Field(lngField, NumberUtils.double2sortableStr(lng),Field.Store.YES, Field.Index.UN_TOKENIZED)); + + // add a default meta field to make searching all documents easy + doc.add(new Field("metafile", "doc",Field.Store.YES, Field.Index.TOKENIZED)); + + int ctpsize = ctps.size(); + for (int i =0; i < ctpsize; i++){ + CartesianTierPlotter ctp = ctps.get(i); + doc.add(new Field(ctp.getTierFieldName(), + NumberUtils.double2sortableStr(ctp.getTierBoxId(lat,lng)), + Field.Store.YES, + Field.Index.NO_NORMS)); + } + writer.addDocument(doc); + + } + + + + private void addData(IndexWriter writer) throws IOException { + addPoint(writer,"McCormick & Schmick's Seafood Restaurant",38.9579000,-77.3572000); + addPoint(writer,"Jimmy's Old Town Tavern",38.9690000,-77.3862000); + addPoint(writer,"Ned Devine's",38.9510000,-77.4107000); + addPoint(writer,"Old Brogue Irish Pub",38.9955000,-77.2884000); + addPoint(writer,"Alf Laylah Wa Laylah",38.8956000,-77.4258000); + addPoint(writer,"Sully's Restaurant & Supper",38.9003000,-77.4467000); + addPoint(writer,"TGIFriday",38.8725000,-77.3829000); + addPoint(writer,"Potomac Swing Dance Club",38.9027000,-77.2639000); + addPoint(writer,"White Tiger Restaurant",38.9027000,-77.2638000); + addPoint(writer,"Jammin' Java",38.9039000,-77.2622000); + addPoint(writer,"Potomac Swing Dance Club",38.9027000,-77.2639000); + addPoint(writer,"WiseAcres Comedy Club",38.9248000,-77.2344000); + addPoint(writer,"Glen Echo Spanish Ballroom",38.9691000,-77.1400000); + addPoint(writer,"Whitlow's on Wilson",38.8889000,-77.0926000); + addPoint(writer,"Iota Club and Cafe",38.8890000,-77.0923000); + addPoint(writer,"Hilton Washington Embassy Row",38.9103000,-77.0451000); + addPoint(writer,"HorseFeathers, Bar & Grill", 39.01220000000001, -77.3942); + writer.flush(); + writer.commit(); + //writer.close(); + } + + public void testRange() throws IOException, InvalidGeoException { + searcher = new IndexSearcher(directory); + + final double miles = 6.0; + + // create a distance query + final DistanceQuery dq = new DistanceQuery(lat, lng, miles, latField, lngField, true); + + System.out.println(dq); + //create a term query to search against all documents + Query tq = new TermQuery(new Term("metafile", "doc")); + + FieldScoreQuery fsQuery = new FieldScoreQuery("geo_distance", Type.FLOAT); + CustomScoreQuery customScore = new CustomScoreQuery(tq,fsQuery){ + + @Override + public float customScore(int doc, float subQueryScore, float valSrcScore){ + //System.out.println(doc); + if (dq.distanceFilter.getDistance(doc) == null) + return 0; + + double distance = dq.distanceFilter.getDistance(doc); + // boost score shouldn't exceed 1 + if (distance < 1.0d) + distance = 1.0d; + //boost by distance is invertly proportional to + // to distance from center point to location + float score = new Float((miles - distance) / miles ).floatValue(); + return score * subQueryScore; + } + }; + // Create a distance sort + // As the radius filter has performed the distance calculations + // already, pass in the filter to reuse the results. + // + DistanceSortSource dsort = new DistanceSortSource(dq.distanceFilter); + Sort sort = new Sort(new SortField("foo", dsort)); + + // Perform the search, using the term query, the serial chain filter, and the + // distance sort + Hits hits = searcher.search(customScore, dq.getFilter()); //,sort); + + int results = hits.length(); + + // Get a list of distances + Map distances = dq.distanceFilter.getDistances(); + + // distances calculated from filter first pass must be less than total + // docs, from the above test of 20 items, 12 will come from the boundary box + // filter, but only 5 are actually in the radius of the results. + + // Note Boundary Box filtering, is not accurate enough for most systems. + + + System.out.println("Distance Filter filtered: " + distances.size()); + System.out.println("Results: " + results); + System.out.println("============================="); + System.out.println("Distances should be 14 "+ distances.size()); + System.out.println("Results should be 7 "+ results); + + assertEquals(14, distances.size()); + assertEquals(7, results); + + for(int i =0 ; i < results; i++){ + Document d = hits.doc(i); + + String name = d.get("name"); + double rsLat = NumberUtils.SortableStr2double(d.get(latField)); + double rsLng = NumberUtils.SortableStr2double(d.get(lngField)); + Double geo_distance = distances.get(hits.id(i)); + + double distance = DistanceUtils.getInstance().getDistanceMi(lat, lng, rsLat, rsLng); + double llm = DistanceUtils.getInstance().getLLMDistance(lat, lng, rsLat, rsLng); + System.out.println("Name: "+ name +", Distance (res, ortho, harvesine):"+ distance +" |"+ geo_distance +"|"+ llm +" | score "+ hits.score(i)); + assertTrue(Math.abs((distance - llm)) < 1); + assertTrue((distance < miles )); + } + } + +} diff --git a/contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestDistance.java b/contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestDistance.java new file mode 100644 index 00000000000..e45cfc592b1 --- /dev/null +++ b/contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestDistance.java @@ -0,0 +1,137 @@ +/** + * 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.io.IOException; +import java.util.Map; + +import junit.framework.TestCase; + +import org.apache.lucene.analysis.WhitespaceAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.Hits; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.SortField; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.spatial.tier.DistanceQuery; +import org.apache.lucene.spatial.tier.DistanceSortSource; +import org.apache.lucene.spatial.tier.DistanceUtils; +import org.apache.lucene.spatial.tier.InvalidGeoException; +import org.apache.lucene.store.RAMDirectory; +import org.apache.lucene.spatial.NumberUtils; + + +/** + * + */ +public class TestDistance extends TestCase{ + + + private RAMDirectory directory; + private IndexSearcher searcher; + // reston va + private double lat = 38.969398; + private double lng= -77.386398; + private String latField = "lat"; + private String lngField = "lng"; + + + @Override + protected void setUp() throws IOException { + directory = new RAMDirectory(); + IndexWriter writer = new IndexWriter(directory, new WhitespaceAnalyzer(), true); + addData(writer); + + } + + + private void addPoint(IndexWriter writer, String name, double lat, double lng) throws IOException{ + + Document doc = new Document(); + + doc.add(new Field("name", name,Field.Store.YES, Field.Index.TOKENIZED)); + + // convert the lat / long to lucene fields + doc.add(new Field(latField, NumberUtils.double2sortableStr(lat),Field.Store.YES, Field.Index.UN_TOKENIZED)); + doc.add(new Field(lngField, NumberUtils.double2sortableStr(lng),Field.Store.YES, Field.Index.UN_TOKENIZED)); + + // add a default meta field to make searching all documents easy + doc.add(new Field("metafile", "doc",Field.Store.YES, Field.Index.TOKENIZED)); + writer.addDocument(doc); + + } + + + private void addData(IndexWriter writer) throws IOException { + addPoint(writer,"McCormick & Schmick's Seafood Restaurant",38.9579000,-77.3572000); + addPoint(writer,"Jimmy's Old Town Tavern",38.9690000,-77.3862000); + addPoint(writer,"Ned Devine's",38.9510000,-77.4107000); + addPoint(writer,"Old Brogue Irish Pub",38.9955000,-77.2884000); + addPoint(writer,"Alf Laylah Wa Laylah",38.8956000,-77.4258000); + addPoint(writer,"Sully's Restaurant & Supper",38.9003000,-77.4467000); + addPoint(writer,"TGIFriday",38.8725000,-77.3829000); + addPoint(writer,"Potomac Swing Dance Club",38.9027000,-77.2639000); + addPoint(writer,"White Tiger Restaurant",38.9027000,-77.2638000); + addPoint(writer,"Jammin' Java",38.9039000,-77.2622000); + addPoint(writer,"Potomac Swing Dance Club",38.9027000,-77.2639000); + addPoint(writer,"WiseAcres Comedy Club",38.9248000,-77.2344000); + addPoint(writer,"Glen Echo Spanish Ballroom",38.9691000,-77.1400000); + addPoint(writer,"Whitlow's on Wilson",38.8889000,-77.0926000); + addPoint(writer,"Iota Club and Cafe",38.8890000,-77.0923000); + addPoint(writer,"Hilton Washington Embassy Row",38.9103000,-77.0451000); + addPoint(writer,"HorseFeathers, Bar & Grill", 39.01220000000001, -77.3942); + writer.flush(); + } + + + + public void testMiles() { + double LLM = DistanceUtils.getInstance().getLLMDistance(lat, lng,39.012200001, -77.3942); + System.out.println(LLM); + System.out.println("-->"+DistanceUtils.getInstance().getDistanceMi(lat, lng, 39.0122, -77.3942)); + } + + public void testMiles2(){ + System.out.println("Test Miles 2"); + double LLM = DistanceUtils.getInstance().getLLMDistance(44.30073, -78.32131,43.687267, -79.39842); + System.out.println(LLM); + System.out.println("-->"+DistanceUtils.getInstance().getDistanceMi(44.30073, -78.32131, 43.687267, -79.39842)); + + } + + +// public void testDistanceQueryCacheable() throws IOException { +// +// // create two of the same distance queries +// double miles = 6.0; +// DistanceQuery dq1 = new DistanceQuery(lat, lng, miles, latField, lngField, true); +// DistanceQuery dq2 = new DistanceQuery(lat, lng, miles, latField, lngField, true); +// +// /* ensure that they hash to the same code, which will cause a cache hit in solr */ +// System.out.println("hash differences?"); +// assertEquals(dq1.getQuery().hashCode(), dq2.getQuery().hashCode()); +// +// /* ensure that changing the radius makes a different hash code, creating a cache miss in solr */ +// DistanceQuery widerQuery = new DistanceQuery(lat, lng, miles + 5.0, latField, lngField, false); +// assertTrue(dq1.getQuery().hashCode() != widerQuery.getQuery().hashCode()); +// } +}