package org.wikibrain.spatial.distance;
import ags.utils.dataStructures.MaxHeap;
import ags.utils.dataStructures.trees.thirdGenKD.KdTree;
import ags.utils.dataStructures.trees.thirdGenKD.SquareEuclideanDistanceFunction;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Point;
import gnu.trove.set.TIntSet;
import org.geotools.referencing.GeodeticCalculator;
import org.wikibrain.core.dao.DaoException;
import org.wikibrain.spatial.constants.Precision;
import org.wikibrain.spatial.dao.SpatialDataDao;
import org.wikibrain.spatial.util.ClosestPointIndex;
import org.wikibrain.spatial.util.WikiBrainSpatialUtils;
import org.wikibrain.utils.ParallelForEach;
import org.wikibrain.utils.Procedure;
import org.wikibrain.utils.WpThreadUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Estimates the number of kilometers between geometries.
*
* @author Shilad Sen
*/
public class SphericalDistanceMetric implements SpatialDistanceMetric {
private static final Logger LOG = LoggerFactory.getLogger(SpatialDistanceMetric.class);
private ClosestPointIndex index;
private final SpatialDataDao spatialDao;
private TIntSet concepts;
public SphericalDistanceMetric(SpatialDataDao spatialDao) {
this.spatialDao = spatialDao;
}
@Override
public void setValidConcepts(TIntSet concepts) {
this.concepts = concepts;
}
public TIntSet getValidConcepts() {
return concepts;
}
/**
* TODO: handle non-point geometries.
* @param enable
* @throws org.wikibrain.core.dao.DaoException
*/
@Override
public void enableCache(boolean enable) throws DaoException {
if (!enable) {
index = null;
return;
}
index = new ClosestPointIndex();
final Map<Integer, Geometry> points = this.spatialDao.getAllGeometriesInLayer("wikidata", Precision.LatLonPrecision.HIGH);
ParallelForEach.loop(points.keySet(), WpThreadUtils.getMaxThreads(),
new Procedure<Integer>() {
@Override
public void call(Integer conceptId) throws Exception {
if (concepts != null && !concepts.contains(conceptId)) {
return;
}
index.insert(conceptId, points.get(conceptId));
}
}, 100000);
LOG.info("loaded " + index.size() + " points");
}
public int getNumConcepts() {
return index.size();
}
@Override
public String getName() {
return "spherical distance metric";
}
@Override
public double distance(Geometry g1, Geometry g2) {
return WikiBrainSpatialUtils.haversine(WikiBrainSpatialUtils.getCenter(g1), WikiBrainSpatialUtils.getCenter(g2));
}
@Override
public float[][] distance(List<Geometry> rowGeometries, List<Geometry> colGeometries) {
Point [] rowPoints = new Point[rowGeometries.size()];
Point [] colPoints = new Point[colGeometries.size()];
for (int i = 0; i < rowGeometries.size(); i++) {
rowPoints[i] = WikiBrainSpatialUtils.getCenter(rowGeometries.get(i));
}
for (int i = 0; i < colGeometries.size(); i++) {
colPoints[i] = WikiBrainSpatialUtils.getCenter(colGeometries.get(i));
}
float [][] matrix = new float[rowGeometries.size()][colGeometries.size()];
for (int i = 0; i < rowGeometries.size(); i++) {
for (int j = 0; j < colGeometries.size(); j++) {
if (rowGeometries.get(i) == colGeometries.get(j) || rowPoints[i].equals(colPoints[j])) {
matrix[i][j] = 0f;
} else {
matrix[i][j] = (float) distance(rowPoints[i], colPoints[j]);
}
}
}
return matrix;
}
@Override
public float[][] distance(List<Geometry> geometries) {
return distance(geometries, geometries);
}
@Override
public List<Neighbor> getNeighbors(Geometry g, int maxNeighbors) {
return getNeighbors(g, maxNeighbors, Double.MAX_VALUE);
}
/**
* A fast approximation of the distance between neighbors based on the 3D straight line distance.
* @param g
* @param maxNeighbors
* @return
*/
public List<Neighbor> getNeighbors(Geometry g, int maxNeighbors, double maxDistance) {
List<Neighbor> results = new ArrayList<Neighbor>();
for (ClosestPointIndex.Result r : index.query(g, maxNeighbors)) {
if (r.distance <= maxDistance) {
results.add(new Neighbor(r.id, r.distance));
}
}
Collections.sort(results);
return results;
}
public ClosestPointIndex getIndex() {
return index;
}
}