package org.wikibrain.spatial.distance;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Point;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.set.TIntSet;
import org.wikibrain.core.dao.DaoException;
import org.wikibrain.spatial.constants.Precision;
import org.wikibrain.spatial.dao.SpatialDataDao;
import org.wikibrain.spatial.util.WikiBrainSpatialUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* Given concepts c1 and c2, the ordinal distance from c1 to c2 is c2's rank in spherical distance.
* The distance of c1 to itself will be 0; the distance of c1 to it's closest neighbor(s) will be 1, etc.
*
* Ordinal distance is calculated exactly up to some threshold of nearest neighbors.
* The default is to exactly calculate ranks to the closest 5% of geometric entities.
* Beyond this distance, it is estimated based on the distance from the furthest known
* neighbor (e.g. the 5% boundary) to the furthest possible neighbor (e.g. earth's circumference / 2).
*
* @author Shilad Sen
*/
public class OrdinalDistanceMetric implements SpatialDistanceMetric {
private static final int MAX_EXACT_DISTANCE = 10000;
private static final int SAMPLE_SIZE = 1000;
private static final Logger LOG = LoggerFactory.getLogger(GraphDistanceMetric.class);
private final SpatialDataDao spatialDao;
private final TIntObjectMap<TIntSet> adjacencyList = new TIntObjectHashMap<TIntSet>();
private final SphericalDistanceMetric spherical;
private TIntSet concepts;
private double fractionRankedExactly = 0.10;
private double maxDistance = WikiBrainSpatialUtils.EARTH_CIRCUMFERENCE / 2;
public OrdinalDistanceMetric(SpatialDataDao dao, SphericalDistanceMetric spherical) throws DaoException {
this.spatialDao = dao;
this.spherical = spherical;
}
@Override
public List<SpatialDistanceMetric.Neighbor> getNeighbors(Geometry g, int maxNeighbors) {
return getNeighbors(g, maxNeighbors, Double.MAX_VALUE);
}
@Override
public List<SpatialDistanceMetric.Neighbor> getNeighbors(Geometry g, int maxNeighbors, double maxDistance) {
int k = maxNeighbors;
if (concepts != null) {
k = Math.max(k, maxNeighbors);
}
List<SpatialDistanceMetric.Neighbor> sphericalNeighbors = spherical.getNeighbors(g, k, Double.MAX_VALUE);
List<SpatialDistanceMetric.Neighbor> result = new ArrayList<Neighbor>();
for (int i = 0; i < sphericalNeighbors.size() && result.size() < maxNeighbors; i++) {
Neighbor n = sphericalNeighbors.get(i);
if (concepts == null || concepts.contains(n.conceptId)) {
result.add(new Neighbor(n.conceptId, i));
}
}
return result;
}
@Override
public void setValidConcepts(TIntSet concepts) {
this.concepts = concepts;
}
@Override
public void enableCache(boolean enable) throws DaoException {
}
@Override
public String getName() {
return "ordinal distance metric";
}
@Override
public double distance(Geometry g1, Geometry g2) {
double []n = getNeighborDistances(g1);
double d = spherical.distance(g1, g2);
return estimateOrdinalDistance(n, d);
}
private double estimateOrdinalDistance(double [] neighborDistances, double sphericalDist) {
int numConcepts = spherical.getNumConcepts();
if (neighborDistances.length > numConcepts) {
throw new IllegalStateException();
}
double furthest = neighborDistances[neighborDistances.length-1];
if (sphericalDist >= furthest) {
double ds = sphericalDist - furthest;
double maxds = maxDistance - furthest;
int remainingConcepts = numConcepts - neighborDistances.length;
return neighborDistances.length + ds / maxds * remainingConcepts;
} else {
int i = Arrays.binarySearch(neighborDistances, sphericalDist);
if (i < 0) {
i = -i - 1;
}
return i;
}
}
private double[] getNeighborDistances(Geometry g) {
List<Neighbor> neighbors = spherical.getNeighbors(g, getMaxNeighbors());
double distances[] = new double[neighbors.size()];
for (int i = 0; i < neighbors.size(); i++) {
distances[i] = neighbors.get(i).distance;
}
return distances;
}
private int getMaxNeighbors() {
return (int) (fractionRankedExactly * spherical.getNumConcepts());
}
@Override
public float[][] distance(List<Geometry> rowGeometries, List<Geometry> colGeometries) {
int nrows = rowGeometries.size();
int ncols = colGeometries.size();
float result[][] = spherical.distance(rowGeometries, colGeometries);
double rowDists[][] = new double[nrows][];
// double colDists[][] = new double[ncols][];
for (int i = 0; i < nrows; i++) {
rowDists[i] = getNeighborDistances(rowGeometries.get(i));
}
// for (int i = 0; i < ncols; i++) {
// colDists[i] = getNeighborDistances(colGeometries.get(i));
// }
for (int i = 0; i < nrows; i++) {
for (int j = 0; j < ncols; j++) {
result[i][j] = (float) estimateOrdinalDistance(rowDists[i], result[i][j]);
}
}
return result;
}
@Override
public float[][] distance(List<Geometry> geometries) {
return distance(geometries, geometries);
}
public void setMaxDistance(double maxDistance) {
this.maxDistance = maxDistance;
}
public double getFractionRankedExactly() {
return fractionRankedExactly;
}
}