();
+ BufferedReader bufInput = new BufferedReader(new InputStreamReader(in,"UTF-8"));
+ try {
+ String line;
+ while ((line = bufInput.readLine()) != null) {
+ if (line.length() == 0 || line.charAt(0) == '#')
+ continue;
+
+ SpatialTestData data = new SpatialTestData();
+ String[] vals = line.split("\t");
+ if (vals.length != 3)
+ throw new RuntimeException("bad format; expecting 3 tab-separated values for line: "+line);
+ data.id = vals[0];
+ data.name = vals[1];
+ try {
+ data.shape = ctx.readShapeFromWkt(vals[2]);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ results.add(data);
+ }
+ } finally {
+ bufInput.close();
+ }
+ return results.iterator();
+ }
+}
Index: lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialArgsParser.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialArgsParser.java (revision 4a34493358cdbdacd069e4cf0a74d05ad0d6f5fc)
+++ lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialArgsParser.java (revision )
@@ -19,9 +19,9 @@
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.exception.InvalidShapeException;
-import com.spatial4j.core.io.ShapeReadWriter;
import com.spatial4j.core.shape.Shape;
+import java.text.ParseException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@@ -30,11 +30,11 @@
/**
* Parses a string that usually looks like "OPERATION(SHAPE)" into a {@link SpatialArgs}
* object. The set of operations supported are defined in {@link SpatialOperation}, such
- * as "Intersects" being a common one. The shape portion is defined by {@link
- * ShapeReadWriter#readShape(String)}. There are some optional name-value pair parameters
- * that follow the closing parenthesis. Example:
+ * as "Intersects" being a common one. The shape portion is defined by WKT {@link com.spatial4j.core.io.WktShapeParser},
+ * but it can be overridden/customized via {@link #parseShape(String, com.spatial4j.core.context.SpatialContext)}.
+ * There are some optional name-value pair parameters that follow the closing parenthesis. Example:
*
- * Intersects(-10,20,-8,22) distErrPct=0.025
+ * Intersects(ENVELOPE(-10,-8,22,20)) distErrPct=0.025
*
*
* In the future it would be good to support something at least semi-standardized like a
@@ -63,45 +63,58 @@
}
/**
- * Parses a string such as "Intersects(-10,20,-8,22) distErrPct=0.025".
+ * Parses a string such as "Intersects(ENVELOPE(-10,-8,22,20)) distErrPct=0.025".
*
* @param v The string to parse. Mandatory.
* @param ctx The spatial context. Mandatory.
* @return Not null.
- * @throws IllegalArgumentException If there is a problem parsing the string.
- * @throws InvalidShapeException Thrown from {@link ShapeReadWriter#readShape(String)}
+ * @throws IllegalArgumentException if the parameters don't make sense or an add-on parameter is unknown
+ * @throws ParseException If there is a problem parsing the string
+ * @throws InvalidShapeException When the coordinates are invalid for the shape
*/
- public SpatialArgs parse(String v, SpatialContext ctx) throws IllegalArgumentException, InvalidShapeException {
+ public SpatialArgs parse(String v, SpatialContext ctx) throws ParseException, InvalidShapeException {
int idx = v.indexOf('(');
int edx = v.lastIndexOf(')');
if (idx < 0 || idx > edx) {
- throw new IllegalArgumentException("missing parens: " + v, null);
+ throw new ParseException("missing parens: " + v, -1);
}
SpatialOperation op = SpatialOperation.get(v.substring(0, idx).trim());
String body = v.substring(idx + 1, edx).trim();
if (body.length() < 1) {
- throw new IllegalArgumentException("missing body : " + v, null);
+ throw new ParseException("missing body : " + v, idx + 1);
}
- Shape shape = ctx.readShape(body);
+ Shape shape = parseShape(body, ctx);
- SpatialArgs args = new SpatialArgs(op, shape);
+ SpatialArgs args = newSpatialArgs(op, shape);
if (v.length() > (edx + 1)) {
body = v.substring(edx + 1).trim();
if (body.length() > 0) {
Map aa = parseMap(body);
- args.setDistErrPct(readDouble(aa.remove(DIST_ERR_PCT)));
- args.setDistErr(readDouble(aa.remove(DIST_ERR)));
+ readNameValuePairs(args, aa);
if (!aa.isEmpty()) {
- throw new IllegalArgumentException("unused parameters: " + aa, null);
+ throw new IllegalArgumentException("unused parameters: " + aa);
}
}
}
args.validate();
return args;
+ }
+
+ protected SpatialArgs newSpatialArgs(SpatialOperation op, Shape shape) {
+ return new SpatialArgs(op, shape);
+ }
+
+ protected void readNameValuePairs(SpatialArgs args, Map nameValPairs) {
+ args.setDistErrPct(readDouble(nameValPairs.remove(DIST_ERR_PCT)));
+ args.setDistErr(readDouble(nameValPairs.remove(DIST_ERR)));
+ }
+
+ protected Shape parseShape(String str, SpatialContext ctx) throws ParseException {
+ return ctx.readShapeFromWkt(str);
}
protected static Double readDouble(String v) {
Index: solr/core/src/java/org/apache/solr/schema/LatLonType.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- solr/core/src/java/org/apache/solr/schema/LatLonType.java (revision 4a34493358cdbdacd069e4cf0a74d05ad0d6f5fc)
+++ solr/core/src/java/org/apache/solr/schema/LatLonType.java (revision )
@@ -22,6 +22,7 @@
import java.util.Map;
import java.util.Set;
+import com.spatial4j.core.shape.Point;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.IndexReader;
@@ -49,9 +50,8 @@
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
-import com.spatial4j.core.exception.InvalidShapeException;
-import com.spatial4j.core.io.ParseUtils;
import com.spatial4j.core.shape.Rectangle;
+import org.apache.solr.util.SpatialUtils;
/**
@@ -71,24 +71,16 @@
@Override
public List createFields(SchemaField field, Object value, float boost) {
String externalVal = value.toString();
- //we could have tileDiff + 3 fields (two for the lat/lon, one for storage)
+ //we could have 3 fields (two for the lat & lon, one for storage)
List f = new ArrayList(3);
if (field.indexed()) {
- int i = 0;
- double[] latLon;
- try {
- latLon = ParseUtils.parseLatitudeLongitude(null, externalVal);
- } catch (InvalidShapeException e) {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
- }
+ Point point = SpatialUtils.parsePointSolrException(externalVal, SpatialContext.GEO);
//latitude
- SchemaField lat = subField(field, i, schema);
- f.add(lat.createField(String.valueOf(latLon[LAT]), lat.indexed() && !lat.omitNorms() ? boost : 1f));
- i++;
+ SchemaField subLatSF = subField(field, LAT, schema);
+ f.add(subLatSF.createField(String.valueOf(point.getY()), subLatSF.indexed() && !subLatSF.omitNorms() ? boost : 1f));
//longitude
- SchemaField lon = subField(field, i, schema);
- f.add(lon.createField(String.valueOf(latLon[LON]), lon.indexed() && !lon.omitNorms() ? boost : 1f));
-
+ SchemaField subLonSF = subField(field, LON, schema);
+ f.add(subLonSF.createField(String.valueOf(point.getX()), subLonSF.indexed() && !subLonSF.omitNorms() ? boost : 1f));
}
if (field.stored()) {
@@ -102,59 +94,43 @@
@Override
public Query getRangeQuery(QParser parser, SchemaField field, String part1, String part2, boolean minInclusive, boolean maxInclusive) {
- int dimension = 2;
+ Point p1 = SpatialUtils.parsePointSolrException(part1, SpatialContext.GEO);
+ Point p2 = SpatialUtils.parsePointSolrException(part2, SpatialContext.GEO);
- String[] p1;
- String[] p2;
- try {
- p1 = ParseUtils.parsePoint(null, part1, dimension);
- p2 = ParseUtils.parsePoint(null, part2, dimension);
- } catch (InvalidShapeException e) {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
- }
+ SchemaField latSF = subField(field, LAT, parser.getReq().getSchema());
+ SchemaField lonSF = subField(field, LON, parser.getReq().getSchema());
BooleanQuery result = new BooleanQuery(true);
- for (int i = 0; i < dimension; i++) {
- SchemaField subSF = subField(field, i, parser.getReq().getSchema());
- // points must currently be ordered... should we support specifying any two opposite corner points?
+ // points must currently be ordered... should we support specifying any two opposite corner points?
- result.add(subSF.getType().getRangeQuery(parser, subSF, p1[i], p2[i], minInclusive, maxInclusive), BooleanClause.Occur.MUST);
- }
+ result.add(latSF.getType().getRangeQuery(parser, latSF,
+ Double.toString(p1.getY()), Double.toString(p2.getY()), minInclusive, maxInclusive), BooleanClause.Occur.MUST);
+ result.add(lonSF.getType().getRangeQuery(parser, lonSF,
+ Double.toString(p1.getX()), Double.toString(p2.getX()), minInclusive, maxInclusive), BooleanClause.Occur.MUST);
return result;
-
}
@Override
public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
- int dimension = 2;
+ Point p1 = SpatialUtils.parsePointSolrException(externalVal, SpatialContext.GEO);
-
+
- String[] p1 = new String[0];
- try {
- p1 = ParseUtils.parsePoint(null, externalVal, dimension);
- } catch (InvalidShapeException e) {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
+ SchemaField latSF = subField(field, LAT, parser.getReq().getSchema());
+ SchemaField lonSF = subField(field, LON, parser.getReq().getSchema());
+ BooleanQuery result = new BooleanQuery(true);
+ result.add(latSF.getType().getFieldQuery(parser, latSF,
+ Double.toString(p1.getY())), BooleanClause.Occur.MUST);
+ result.add(lonSF.getType().getFieldQuery(parser, lonSF,
+ Double.toString(p1.getX())), BooleanClause.Occur.MUST);
+ return result;
- }
+ }
- BooleanQuery bq = new BooleanQuery(true);
- for (int i = 0; i < dimension; i++) {
- SchemaField sf = subField(field, i, parser.getReq().getSchema());
- Query tq = sf.getType().getFieldQuery(parser, sf, p1[i]);
- bq.add(tq, BooleanClause.Occur.MUST);
- }
- return bq;
- }
@Override
public Query createSpatialQuery(QParser parser, SpatialOptions options) {
- double[] point = null;
- try {
- point = ParseUtils.parseLatitudeLongitude(options.pointStr);
- } catch (InvalidShapeException e) {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
- }
+ Point point = SpatialUtils.parsePointSolrException(options.pointStr, SpatialContext.GEO);
// lat & lon in degrees
- double latCenter = point[LAT];
- double lonCenter = point[LON];
+ double latCenter = point.getY();
+ double lonCenter = point.getX();
double distDeg = DistanceUtils.dist2Degrees(options.distance, options.radius);
Rectangle bbox = DistanceUtils.calcBoxByDistFromPtDEG(latCenter, lonCenter, distDeg, SpatialContext.GEO, null);
@@ -176,8 +152,8 @@
IndexSchema schema = parser.getReq().getSchema();
// Now that we've figured out the ranges, build them!
- SchemaField latField = subField(options.field, LAT, schema);
- SchemaField lonField = subField(options.field, LON, schema);
+ SchemaField latSF = subField(options.field, LAT, schema);
+ SchemaField lonSF = subField(options.field, LON, schema);
SpatialDistanceQuery spatial = new SpatialDistanceQuery();
@@ -185,14 +161,14 @@
if (options.bbox) {
BooleanQuery result = new BooleanQuery();
- Query latRange = latField.getType().getRangeQuery(parser, latField,
+ Query latRange = latSF.getType().getRangeQuery(parser, latSF,
String.valueOf(latMin),
String.valueOf(latMax),
true, true);
result.add(latRange, BooleanClause.Occur.MUST);
if (lonMin != -180 || lonMax != 180) {
- Query lonRange = lonField.getType().getRangeQuery(parser, lonField,
+ Query lonRange = lonSF.getType().getRangeQuery(parser, lonSF,
String.valueOf(lonMin),
String.valueOf(lonMax),
true, true);
@@ -201,7 +177,7 @@
BooleanQuery bothLons = new BooleanQuery();
bothLons.add(lonRange, BooleanClause.Occur.SHOULD);
- lonRange = lonField.getType().getRangeQuery(parser, lonField,
+ lonRange = lonSF.getType().getRangeQuery(parser, lonSF,
String.valueOf(lon2Min),
String.valueOf(lon2Max),
true, true);
@@ -218,8 +194,8 @@
spatial.origField = options.field.getName();
- spatial.latSource = latField.getType().getValueSource(latField, parser);
- spatial.lonSource = lonField.getType().getValueSource(lonField, parser);
+ spatial.latSource = latSF.getType().getValueSource(latSF, parser);
+ spatial.lonSource = lonSF.getType().getValueSource(lonSF, parser);
spatial.latMin = latMin;
spatial.latMax = latMax;
spatial.lonMin = lonMin;
Index: solr/core/src/test/org/apache/solr/search/TestSolr4Spatial.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- solr/core/src/test/org/apache/solr/search/TestSolr4Spatial.java (revision 4a34493358cdbdacd069e4cf0a74d05ad0d6f5fc)
+++ solr/core/src/test/org/apache/solr/search/TestSolr4Spatial.java (revision )
@@ -21,12 +21,16 @@
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
+import com.spatial4j.core.shape.Point;
+import com.spatial4j.core.shape.Rectangle;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
+import org.apache.solr.util.SpatialUtils;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
+import java.text.ParseException;
import java.util.Arrays;
/**
@@ -151,20 +155,20 @@
assertQ(req(
"fl", "id," + fieldName, "q", "*:*", "rows", "1000",
- "fq", "{!field f="+fieldName+"}Intersects(Circle(89.9,-130 d=9))"),
+ "fq", "{!geofilt sfield="+fieldName+" pt="+IN+" d=9}"),
"//result/doc/*[@name='" + fieldName + "']//text()='" + OUT + "'");
}
@Test
- public void checkQueryEmptyIndex() {
+ public void checkQueryEmptyIndex() throws ParseException {
checkHits(fieldName, "0,0", 100, 0);//doesn't error
}
- private void checkHits(String fieldName, String pt, double distKM, int count, int ... docIds) {
+ private void checkHits(String fieldName, String pt, double distKM, int count, int ... docIds) throws ParseException {
checkHits(fieldName, true, pt, distKM, count, docIds);
}
- private void checkHits(String fieldName, boolean exact, String ptStr, double distKM, int count, int ... docIds) {
+ private void checkHits(String fieldName, boolean exact, String ptStr, double distKM, int count, int ... docIds) throws ParseException {
String [] tests = new String[docIds != null && docIds.length > 0 ? docIds.length + 1 : 1];
//test for presence of required ids first
int i = 0;
@@ -177,20 +181,23 @@
// that there may be a more specific detailed id to investigate.
tests[i++] = "*[count(//doc)=" + count + "]";
- //Test using the Solr 4 syntax
+ //Test using the Lucene spatial syntax
{
//never actually need the score but lets test
String score = new String[]{null, "none","distance","recipDistance"}[random().nextInt(4)];
double distDEG = DistanceUtils.dist2Degrees(distKM, DistanceUtils.EARTH_MEAN_RADIUS_KM);
- String circleStr = "Circle(" + ptStr.replaceAll(" ", "") + " d=" + distDEG + ")";
+ Point point = SpatialUtils.parsePoint(ptStr, SpatialContext.GEO);
+ String circleStr = "BUFFER(POINT(" + point.getX()+" "+point.getY()+")," + distDEG + ")";
String shapeStr;
if (exact) {
shapeStr = circleStr;
} else {//bbox
//the GEO is an assumption
SpatialContext ctx = SpatialContext.GEO;
- shapeStr = ctx.toString( ctx.readShape(circleStr).getBoundingBox() );
+ Rectangle bbox = ctx.readShapeFromWkt(circleStr).getBoundingBox();
+ shapeStr = "ENVELOPE(" + bbox.getMinX() + ", " + bbox.getMaxX() +
+ ", " + bbox.getMaxY() + ", " + bbox.getMinY() + ")";
}
//FYI default distErrPct=0.025 works with the tests in this file
@@ -200,7 +207,7 @@
+ "}Intersects(" + shapeStr + ")"),
tests);
}
- //Test using the Solr 3 syntax
+ //Test using geofilt
{
assertQ(req(
"fl", "id", "q", "*:*", "rows", "1000",
@@ -219,8 +226,8 @@
String score = random().nextBoolean() ? "none" : "distance";//never actually need the score but lets test
assertQ(req(
- "fl", "id", "q","*:*", "rows", "1000",
- "fq", "{! score="+score+" df="+fieldName+"}[32,-80 TO 33,-79]"),//lower-left to upper-right
+ "fl", "id", "q","*:*", "rows", "1000", // testing quotes in range too
+ "fq", "{! score="+score+" df="+fieldName+"}[32,-80 TO \"33 , -79\"]"),//lower-left to upper-right
"//result/doc/*[@name='id'][.='" + docId + "']",
"*[count(//doc)=" + count + "]");
@@ -234,8 +241,9 @@
assertU(commit());
//test absence of score=distance means it doesn't score
+
assertJQ(req(
- "q", fieldName +":\"Intersects(Circle(3,4 d=9))\"",
+ "q", radiusQuery(3, 4, 9, null, null),
"fl","id,score")
, 1e-9
, "/response/docs/[0]/score==1.0"
@@ -244,7 +252,7 @@
//score by distance
assertJQ(req(
- "q", "{! score=distance}"+fieldName +":\"Intersects(Circle(3,4 d=9))\"",
+ "q", radiusQuery(3, 4, 9, "distance", null),
"fl","id,score",
"sort","score asc")//want ascending due to increasing distance
, 1e-3
@@ -255,7 +263,7 @@
);
//score by recipDistance
assertJQ(req(
- "q", "{! score=recipDistance}"+fieldName +":\"Intersects(Circle(3,4 d=9))\"",
+ "q", radiusQuery(3, 4, 9, "recipDistance", null),
"fl","id,score",
"sort","score desc")//want descending
, 1e-3
@@ -268,7 +276,7 @@
//score by distance and don't filter
assertJQ(req(
//circle radius is small and shouldn't match either, but we disable filtering
- "q", "{! score=distance filter=false}"+fieldName +":\"Intersects(Circle(3,4 d=0.000001))\"",
+ "q", radiusQuery(3, 4, 0.000001, "distance", "false"),
"fl","id,score",
"sort","score asc")//want ascending due to increasing distance
, 1e-3
@@ -280,7 +288,7 @@
//query again with the query point closer to #101, and check the new ordering
assertJQ(req(
- "q", "{! score=distance}"+fieldName +":\"Intersects(Circle(4,0 d=9))\"",
+ "q", radiusQuery(4, 0, 9, "distance", null),
"fl","id,score",
"sort","score asc")//want ascending due to increasing distance
, 1e-4
@@ -293,7 +301,7 @@
"q","-id:999",//exclude that doc
"fl","id,score",
"sort","query($sortQuery) asc", //want ascending due to increasing distance
- "sortQuery", "{! score=distance}"+fieldName +":\"Intersects(Circle(3,4 d=9))\"" )
+ "sortQuery", radiusQuery(3, 4, 9, "distance", null))
, 1e-4
, "/response/docs/[0]/id=='100'"
, "/response/docs/[1]/id=='101'" );
@@ -303,12 +311,28 @@
"q","-id:999",//exclude that doc
"fl","id,score",
"sort","query($sortQuery) asc", //want ascending due to increasing distance
- "sortQuery", "{! score=distance}"+fieldName +":\"Intersects(Circle(4,0 d=9))\"" )
+ "sortQuery", radiusQuery(4, 0, 9, "distance", null))
, 1e-4
, "/response/docs/[0]/id=='101'"
, "/response/docs/[1]/id=='100'" );
}
+ private String radiusQuery(double lat, double lon, double dDEG, String score, String filter) {
+ //Choose between the Solr/Geofilt syntax, and the Lucene spatial module syntax
+ if (random().nextBoolean()) {
+ return "{!geofilt " +
+ "sfield=" + fieldName + " "
+ + (score != null ? "score="+score : "") + " "
+ + (filter != null ? "filter="+filter : "") + " "
+ + "pt=" + lat + "," + lon + " d=" + (dDEG * DistanceUtils.DEG_TO_KM) + "}";
+ } else {
+ return "{! "
+ + (score != null ? "score="+score : "") + " "
+ + (filter != null ? "filter="+filter : "") + " "
+ + "}" + fieldName + ":\"Intersects(BUFFER(POINT(" + lon + " " + lat + ")," + dDEG + "))\"";
+ }
+ }
+
@Test
public void testSortMultiVal() throws Exception {
RandomizedTest.assumeFalse("Multivalue not supported for this field", fieldName.equals("pointvector"));
@@ -318,13 +342,31 @@
assertU(commit());
assertJQ(req(
- "q", "{! score=distance}"+fieldName +":\"Intersects(Circle(3,4 d=9))\"",
+ "q", radiusQuery(3, 4, 9, "distance", null),
"fl","id,score",
"sort","score asc")//want ascending due to increasing distance
, 1e-4
, "/response/docs/[0]/id=='101'"
, "/response/docs/[0]/score==0.99862987"//dist to 3,5
);
+ }
+
+ @Test
+ public void solr4OldShapeSyntax() throws Exception {
+ assumeFalse("Mostly just valid for prefix-tree", fieldName.equals("pointvector"));
+ //syntax supported in Solr 4 but not beyond
+ // See Spatial4j LegacyShapeReadWriterFormat
+ String rect = "-74.093 41.042 -69.347 44.558";//minX minY maxX maxY
+ String circ = "Circle(4.56,1.23 d=0.0710)";
+
+ //show we can index this (without an error)
+ assertU(adoc("id", "rect", fieldName, rect));
+ assertU(adoc("id", "circ", fieldName, circ));
+ assertU(commit());
+
+ //only testing no error
+ assertJQ(req("q", "{!field f=" + fieldName + "}Intersects(" + rect + ")"));
+ assertJQ(req("q", "{!field f=" + fieldName + "}Intersects(" + circ + ")"));
}
}
Index: solr/core/src/java/org/apache/solr/search/function/distance/VectorDistanceFunction.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- solr/core/src/java/org/apache/solr/search/function/distance/VectorDistanceFunction.java (revision 4a34493358cdbdacd069e4cf0a74d05ad0d6f5fc)
+++ solr/core/src/java/org/apache/solr/search/function/distance/VectorDistanceFunction.java (revision )
@@ -22,7 +22,6 @@
import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
import org.apache.lucene.queries.function.valuesource.MultiValueSource;
import org.apache.lucene.search.IndexSearcher;
-import com.spatial4j.core.distance.DistanceUtils;
import org.apache.solr.common.SolrException;
import java.io.IOException;
@@ -75,7 +74,79 @@
double[] vals2 = new double[source1.dimension()];
dv1.doubleVal(doc, vals1);
dv2.doubleVal(doc, vals2);
- return DistanceUtils.vectorDistance(vals1, vals2, power, oneOverPower);
+ return vectorDistance(vals1, vals2, power, oneOverPower);
+ }
+
+ /**
+ * Calculate the p-norm (i.e. length) between two vectors.
+ *
+ * See Lp space
+ *
+ * @param vec1 The first vector
+ * @param vec2 The second vector
+ * @param power The power (2 for cartesian distance, 1 for manhattan, etc.)
+ * @return The length.
+ *
+ * @see #vectorDistance(double[], double[], double, double)
+ *
+ */
+ public static double vectorDistance(double[] vec1, double[] vec2, double power) {
+ //only calc oneOverPower if it's needed
+ double oneOverPower = (power == 0 || power == 1.0 || power == 2.0) ? Double.NaN : 1.0 / power;
+ return vectorDistance(vec1, vec2, power, oneOverPower);
+ }
+
+ /**
+ * Calculate the p-norm (i.e. length) between two vectors.
+ *
+ * @param vec1 The first vector
+ * @param vec2 The second vector
+ * @param power The power (2 for cartesian distance, 1 for manhattan, etc.)
+ * @param oneOverPower If you've pre-calculated oneOverPower and cached it, use this method to save
+ * one division operation over {@link #vectorDistance(double[], double[], double)}.
+ * @return The length.
+ */
+ public static double vectorDistance(double[] vec1, double[] vec2, double power, double oneOverPower) {
+ double result = 0;
+
+ if (power == 0) {
+ for (int i = 0; i < vec1.length; i++) {
+ result += vec1[i] - vec2[i] == 0 ? 0 : 1;
+ }
+ } else if (power == 1.0) { // Manhattan
+ for (int i = 0; i < vec1.length; i++) {
+ result += Math.abs(vec1[i] - vec2[i]);
+ }
+ } else if (power == 2.0) { // Cartesian
+ result = Math.sqrt(distSquaredCartesian(vec1, vec2));
+ } else if (power == Integer.MAX_VALUE || Double.isInfinite(power)) {//infinite norm?
+ for (int i = 0; i < vec1.length; i++) {
+ result = Math.max(result, Math.max(vec1[i], vec2[i]));
+ }
+ } else {
+ for (int i = 0; i < vec1.length; i++) {
+ result += Math.pow(vec1[i] - vec2[i], power);
+ }
+ result = Math.pow(result, oneOverPower);
+ }
+ return result;
+ }
+
+ /**
+ * The square of the cartesian Distance. Not really a distance, but useful if all that matters is
+ * comparing the result to another one.
+ *
+ * @param vec1 The first point
+ * @param vec2 The second point
+ * @return The squared cartesian distance
+ */
+ public static double distSquaredCartesian(double[] vec1, double[] vec2) {
+ double result = 0;
+ for (int i = 0; i < vec1.length; i++) {
+ double v = vec1[i] - vec2[i];
+ result += v * v;
+ }
+ return result;
}
@Override