/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch 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.elasticsearch.index.mapper.geo;
import gnu.trove.list.array.TDoubleArrayList;
import org.apache.lucene.index.IndexReader;
import org.elasticsearch.common.RamUsage;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.util.concurrent.ThreadLocals;
import org.elasticsearch.index.field.data.FieldData;
import org.elasticsearch.index.field.data.FieldDataType;
import org.elasticsearch.index.field.data.support.FieldDataLoader;
import org.elasticsearch.index.search.geo.GeoDistance;
import org.elasticsearch.index.search.geo.GeoHashUtils;
import java.io.IOException;
/**
*
*/
public abstract class GeoPointFieldData extends FieldData<GeoPointDocFieldData> {
static ThreadLocal<ThreadLocals.CleanableValue<GeoPoint>> valuesCache = new ThreadLocal<ThreadLocals.CleanableValue<GeoPoint>>() {
@Override
protected ThreadLocals.CleanableValue<GeoPoint> initialValue() {
return new ThreadLocals.CleanableValue<GeoPoint>(new GeoPoint());
}
};
static class GeoPointHash {
public double lat;
public double lon;
public String geoHash = "";
}
static ThreadLocal<ThreadLocals.CleanableValue<GeoPointHash>> geoHashCache = new ThreadLocal<ThreadLocals.CleanableValue<GeoPointHash>>() {
@Override
protected ThreadLocals.CleanableValue<GeoPointHash> initialValue() {
return new ThreadLocals.CleanableValue<GeoPointHash>(new GeoPointHash());
}
};
public static final GeoPoint[] EMPTY_ARRAY = new GeoPoint[0];
protected final double[] lat;
protected final double[] lon;
protected GeoPointFieldData(String fieldName, double[] lat, double[] lon) {
super(fieldName);
this.lat = lat;
this.lon = lon;
}
abstract public GeoPoint value(int docId);
abstract public GeoPoint[] values(int docId);
abstract public double latValue(int docId);
abstract public double lonValue(int docId);
abstract public double[] latValues(int docId);
abstract public double[] lonValues(int docId);
public double distance(int docId, DistanceUnit unit, double lat, double lon) {
return GeoDistance.PLANE.calculate(latValue(docId), lonValue(docId), lat, lon, unit);
}
public double arcDistance(int docId, DistanceUnit unit, double lat, double lon) {
return GeoDistance.ARC.calculate(latValue(docId), lonValue(docId), lat, lon, unit);
}
public double factorDistance(int docId, DistanceUnit unit, double lat, double lon) {
return GeoDistance.FACTOR.calculate(latValue(docId), lonValue(docId), lat, lon, unit);
}
public double distanceGeohash(int docId, DistanceUnit unit, String geoHash) {
GeoPointHash geoPointHash = geoHashCache.get().get();
if (geoPointHash.geoHash != geoHash) {
geoPointHash.geoHash = geoHash;
double[] decode = GeoHashUtils.decode(geoHash);
geoPointHash.lat = decode[0];
geoPointHash.lon = decode[1];
}
return GeoDistance.PLANE.calculate(latValue(docId), lonValue(docId), geoPointHash.lat, geoPointHash.lon, unit);
}
@Override
public GeoPointDocFieldData docFieldData(int docId) {
return super.docFieldData(docId);
}
@Override
protected long computeSizeInBytes() {
return (RamUsage.NUM_BYTES_DOUBLE * lat.length + RamUsage.NUM_BYTES_ARRAY_HEADER) +
(RamUsage.NUM_BYTES_DOUBLE * lon.length + RamUsage.NUM_BYTES_ARRAY_HEADER);
}
@Override
public String stringValue(int docId) {
return value(docId).geohash();
}
@Override
protected GeoPointDocFieldData createFieldData() {
return new GeoPointDocFieldData(this);
}
@Override
public FieldDataType type() {
return GeoPointFieldDataType.TYPE;
}
@Override
public void forEachValue(StringValueProc proc) {
for (int i = 1; i < lat.length; i++) {
proc.onValue(GeoHashUtils.encode(lat[i], lon[i]));
}
}
public void forEachValue(PointValueProc proc) {
for (int i = 1; i < lat.length; i++) {
GeoPoint point = valuesCache.get().get();
point.latlon(lat[i], lon[i]);
proc.onValue(point);
}
}
public static interface PointValueProc {
void onValue(GeoPoint value);
}
public void forEachValue(ValueProc proc) {
for (int i = 1; i < lat.length; i++) {
proc.onValue(lat[i], lon[i]);
}
}
public static interface ValueProc {
void onValue(double lat, double lon);
}
public abstract void forEachValueInDoc(int docId, ValueInDocProc proc);
public static interface ValueInDocProc {
void onValue(int docId, double lat, double lon);
}
public static GeoPointFieldData load(IndexReader reader, String field) throws IOException {
return FieldDataLoader.load(reader, field, new StringTypeLoader());
}
static class StringTypeLoader extends FieldDataLoader.FreqsTypeLoader<GeoPointFieldData> {
private final TDoubleArrayList lat = new TDoubleArrayList();
private final TDoubleArrayList lon = new TDoubleArrayList();
StringTypeLoader() {
super();
// the first one indicates null value
lat.add(0);
lon.add(0);
}
@Override
public void collectTerm(String term) {
int comma = term.indexOf(',');
lat.add(Double.parseDouble(term.substring(0, comma)));
lon.add(Double.parseDouble(term.substring(comma + 1)));
}
@Override
public GeoPointFieldData buildSingleValue(String field, int[] ordinals) {
return new SingleValueGeoPointFieldData(field, ordinals, lat.toArray(), lon.toArray());
}
@Override
public GeoPointFieldData buildMultiValue(String field, int[][] ordinals) {
return new MultiValueGeoPointFieldData(field, ordinals, lat.toArray(), lon.toArray());
}
}
}