1
package org.apache.solr.search.function.distance;
3
* Licensed to the Apache Software Foundation (ASF) under one or more
4
* contributor license agreements. See the NOTICE file distributed with
5
* this work for additional information regarding copyright ownership.
6
* The ASF licenses this file to You under the Apache License, Version 2.0
7
* (the "License"); you may not use this file except in compliance with
8
* the License. You may obtain a copy of the License at
10
* http://www.apache.org/licenses/LICENSE-2.0
12
* Unless required by applicable law or agreed to in writing, software
13
* distributed under the License is distributed on an "AS IS" BASIS,
14
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
* See the License for the specific language governing permissions and
16
* limitations under the License.
19
import org.apache.lucene.index.IndexReader;
20
import org.apache.lucene.queryParser.ParseException;
21
import org.apache.lucene.search.Searcher;
22
import org.apache.lucene.spatial.DistanceUtils;
23
import org.apache.lucene.spatial.tier.InvalidGeoException;
24
import org.apache.solr.common.params.SpatialParams;
25
import org.apache.solr.schema.SchemaField;
26
import org.apache.solr.search.FunctionQParser;
27
import org.apache.solr.search.ValueSourceParser;
28
import org.apache.solr.search.function.*;
30
import java.io.IOException;
31
import java.util.Arrays;
32
import java.util.List;
37
* Haversine function with one point constant
39
public class HaversineConstFunction extends ValueSource {
41
public static ValueSourceParser parser = new ValueSourceParser() {
43
public ValueSource parse(FunctionQParser fp) throws ParseException
45
// TODO: dispatch through SpatialQueriable in the future?
46
List<ValueSource> sources = fp.parseValueSourceList();
48
// "m" is a multi-value source, "x" is a single-value source
49
// allow (m,m) (m,x,x) (x,x,m) (x,x,x,x)
50
// if not enough points are present, "pt" will be checked first, followed by "sfield".
52
MultiValueSource mv1 = null;
53
MultiValueSource mv2 = null;
55
if (sources.size() == 0) {
57
} else if (sources.size() == 1) {
58
ValueSource vs = sources.get(0);
59
if (!(vs instanceof MultiValueSource)) {
60
throw new ParseException("geodist - invalid parameters:" + sources);
62
mv1 = (MultiValueSource)vs;
63
} else if (sources.size() == 2) {
64
ValueSource vs1 = sources.get(0);
65
ValueSource vs2 = sources.get(1);
67
if (vs1 instanceof MultiValueSource && vs2 instanceof MultiValueSource) {
68
mv1 = (MultiValueSource)vs1;
69
mv2 = (MultiValueSource)vs2;
71
mv1 = makeMV(sources, sources);
73
} else if (sources.size()==3) {
74
ValueSource vs1 = sources.get(0);
75
ValueSource vs2 = sources.get(1);
76
if (vs1 instanceof MultiValueSource) { // (m,x,x)
77
mv1 = (MultiValueSource)vs1;
78
mv2 = makeMV(sources.subList(1,3), sources);
80
mv1 = makeMV(sources.subList(0,2), sources);
82
if (!(vs1 instanceof MultiValueSource)) {
83
throw new ParseException("geodist - invalid parameters:" + sources);
85
mv2 = (MultiValueSource)vs1;
87
} else if (sources.size()==4) {
88
mv1 = makeMV(sources.subList(0,2), sources);
89
mv2 = makeMV(sources.subList(2,4), sources);
90
} else if (sources.size() > 4) {
91
throw new ParseException("geodist - invalid parameters:" + sources);
96
mv2 = parseSfield(fp);
97
} else if (mv2 == null) {
100
mv2 = parseSfield(fp);
103
if (mv1 == null || mv2 == null) {
104
throw new ParseException("geodist - not enough parameters:" + sources);
107
// We have all the parameters at this point, now check if one of the points is constant
109
constants = getConstants(mv1);
110
MultiValueSource other = mv2;
111
if (constants == null) {
112
constants = getConstants(mv2);
116
if (constants != null && other instanceof VectorValueSource) {
117
return new HaversineConstFunction(constants[0], constants[1], (VectorValueSource)other);
120
return new HaversineFunction(mv1, mv2, DistanceUtils.EARTH_MEAN_RADIUS_KM, true);
124
/** make a MultiValueSource from two non MultiValueSources */
125
private static VectorValueSource makeMV(List<ValueSource> sources, List<ValueSource> orig) throws ParseException {
126
ValueSource vs1 = sources.get(0);
127
ValueSource vs2 = sources.get(1);
129
if (vs1 instanceof MultiValueSource || vs2 instanceof MultiValueSource) {
130
throw new ParseException("geodist - invalid parameters:" + orig);
132
return new VectorValueSource(sources);
135
private static MultiValueSource parsePoint(FunctionQParser fp) throws ParseException {
136
String pt = fp.getParam(SpatialParams.POINT);
137
if (pt == null) return null;
138
double[] point = null;
140
point = DistanceUtils.parseLatitudeLongitude(pt);
141
} catch (InvalidGeoException e) {
142
throw new ParseException("Bad spatial pt:" + pt);
144
return new VectorValueSource(Arrays.asList(new ValueSource[] {new DoubleConstValueSource(point[0]),new DoubleConstValueSource(point[1])}));
147
private static double[] getConstants(MultiValueSource vs) {
148
if (!(vs instanceof VectorValueSource)) return null;
149
List<ValueSource> sources = ((VectorValueSource)vs).getSources();
150
if (sources.get(0) instanceof ConstNumberSource && sources.get(1) instanceof ConstNumberSource) {
151
return new double[] { ((ConstNumberSource) sources.get(0)).getDouble(), ((ConstNumberSource) sources.get(1)).getDouble()};
156
private static MultiValueSource parseSfield(FunctionQParser fp) throws ParseException {
157
String sfield = fp.getParam(SpatialParams.FIELD);
158
if (sfield == null) return null;
159
SchemaField sf = fp.getReq().getSchema().getField(sfield);
160
ValueSource vs = sf.getType().getValueSource(sf, fp);
161
if (!(vs instanceof MultiValueSource)) {
162
throw new ParseException("Spatial field must implement MultiValueSource:" + sf);
164
return (MultiValueSource)vs;
168
//////////////////////////////////////////////////////////////////////////////////////
170
private final double latCenter;
171
private final double lonCenter;
172
private final VectorValueSource p2; // lat+lon, just saved for display/debugging
173
private final ValueSource latSource;
174
private final ValueSource lonSource;
176
private final double latCenterRad_cos; // cos(latCenter)
177
private static final double EARTH_MEAN_DIAMETER = DistanceUtils.EARTH_MEAN_RADIUS_KM * 2;
180
public HaversineConstFunction(double latCenter, double lonCenter, VectorValueSource vs) {
181
this.latCenter = latCenter;
182
this.lonCenter = lonCenter;
184
this.latSource = p2.getSources().get(0);
185
this.lonSource = p2.getSources().get(1);
186
this.latCenterRad_cos = Math.cos(latCenter * DistanceUtils.DEGREES_TO_RADIANS);
189
protected String name() {
194
public DocValues getValues(Map context, IndexReader reader) throws IOException {
195
final DocValues latVals = latSource.getValues(context, reader);
196
final DocValues lonVals = lonSource.getValues(context, reader);
197
final double latCenterRad = this.latCenter * DistanceUtils.DEGREES_TO_RADIANS;
198
final double lonCenterRad = this.lonCenter * DistanceUtils.DEGREES_TO_RADIANS;
199
final double latCenterRad_cos = this.latCenterRad_cos;
201
return new DocValues() {
203
public float floatVal(int doc) {
204
return (float) doubleVal(doc);
208
public int intVal(int doc) {
209
return (int) doubleVal(doc);
213
public long longVal(int doc) {
214
return (long) doubleVal(doc);
218
public double doubleVal(int doc) {
219
double latRad = latVals.doubleVal(doc) * DistanceUtils.DEGREES_TO_RADIANS;
220
double lonRad = lonVals.doubleVal(doc) * DistanceUtils.DEGREES_TO_RADIANS;
221
double diffX = latCenterRad - latRad;
222
double diffY = lonCenterRad - lonRad;
223
double hsinX = Math.sin(diffX * 0.5);
224
double hsinY = Math.sin(diffY * 0.5);
225
double h = hsinX * hsinX +
226
(latCenterRad_cos * Math.cos(latRad) * hsinY * hsinY);
227
return (EARTH_MEAN_DIAMETER * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h)));
231
public String strVal(int doc) {
232
return Double.toString(doubleVal(doc));
236
public String toString(int doc) {
237
return name() + '(' + latVals.toString(doc) + ',' + lonVals.toString(doc) + ',' + latCenter + ',' + lonCenter + ')';
243
public void createWeight(Map context, Searcher searcher) throws IOException {
244
latSource.createWeight(context, searcher);
245
lonSource.createWeight(context, searcher);
249
public boolean equals(Object o) {
250
if (!(o instanceof HaversineConstFunction)) return false;
251
HaversineConstFunction other = (HaversineConstFunction) o;
252
return this.latCenter == other.latCenter
253
&& this.lonCenter == other.lonCenter
254
&& this.p2.equals(other.p2);
259
public int hashCode() {
260
int result = p2.hashCode();
262
temp = Double.doubleToRawLongBits(latCenter);
263
result = 31 * result + (int) (temp ^ (temp >>> 32));
264
temp = Double.doubleToRawLongBits(lonCenter);
265
result = 31 * result + (int) (temp ^ (temp >>> 32));
270
public String description() {
271
return name() + '(' + p2 + ',' + latCenter + ',' + lonCenter + ')';