All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wikibrain.spatial.distance.GeodeticDistanceMetric Maven / Gradle / Ivy

There is a newer version: 0.9.1
Show newest version
package org.wikibrain.spatial.distance;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.operation.distance.DistanceOp;
import gnu.trove.set.TIntSet;
import org.geotools.referencing.GeodeticCalculator;
import org.wikibrain.core.dao.DaoException;
import org.wikibrain.spatial.dao.SpatialDataDao;
import org.wikibrain.spatial.util.ClosestPointIndex;
import org.wikibrain.spatial.util.WikiBrainSpatialUtils;

import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Estimates the number of kilometers between geometries.
 *
 * TODO: handle non-points for neighbors.
 *
 * @author Shilad Sen
 */
public class GeodeticDistanceMetric implements SpatialDistanceMetric {
    private static final double EARTH_RADIUS = 6371.0 * 1000;   // radius of the earth in meters

    private static final Logger LOG = LoggerFactory.getLogger(SpatialDistanceMetric.class);
    private final ClosestPointIndex index;
    private final SpatialDataDao spatialDao;
    private TIntSet concepts;
    private boolean useBorders = false;

    /**
     * Creates a new geodetic spatial distance metric.
     *
     * If useBorder is true, it computes the distance between
     * (multi)polygons by finding the closest two points on the boundary.
     *
     * Otherwise it computes distances between centroids.
     *
     * TODO: consider other methods (e.g. capitials,population-weighted centroids, etc).
     *
     * @param spatialDao
     * @param useBorders
     */
    public GeodeticDistanceMetric(SpatialDataDao spatialDao, ClosestPointIndex index, boolean useBorders) {
        this.spatialDao = spatialDao;
        this.index = index;
        this.useBorders = useBorders;
    }

    public GeodeticDistanceMetric(SpatialDataDao spatialDao, SphericalDistanceMetric spherical) {
        this(spatialDao, spherical.getIndex(), false);
    }

    @Override
    public void setValidConcepts(TIntSet concepts) {
        this.concepts = concepts;
    }

    /**
     * TODO: handle non-point geometries.
     * @param enable
     * @throws DaoException
     */
    @Override
    public void enableCache(boolean enable) throws DaoException {
        // Do nothing, for now
    }

    @Override
    public String getName() {
        return "geodetic distance metric";
    }

    @Override
    public double distance(Geometry g1, Geometry g2) {
        return distance(new GeodeticCalculator(), g1, g2);
    }

    @Override
    public float[][] distance(List rowGeometries, List colGeometries) {
        GeodeticCalculator calc = new GeodeticCalculator();
        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)) {
                    matrix[i][j] = 0f;
                } else {
                    matrix[i][j] = (float) distance(calc, rowGeometries.get(i), colGeometries.get(j));
                }
            }
        }
        return matrix;
    }

    @Override
    public float[][] distance(List geometries) {
        return distance(geometries, geometries);
    }

    @Override
    public List getNeighbors(Geometry g, int maxNeighbors) {
        return getNeighbors(g, maxNeighbors, Double.MAX_VALUE);
    }

    /**
     * Returns the closest points to a particular geometry.
     * Note that this ONLY currently uses centroids (FIXME).
     *
     * @param g
     * @param maxNeighbors
     * @param maxDistance
     * @return
     */
    @Override
    public List getNeighbors(Geometry g, int maxNeighbors, double maxDistance) {
        Point c = WikiBrainSpatialUtils.getCenter(g);
        GeodeticCalculator calc = new GeodeticCalculator();
        calc.setStartingGeographicPoint(c.getX(), c.getY());
        List results = new ArrayList();
        for (ClosestPointIndex.Result r: index.query(g, maxNeighbors)) {
            if (r.distance < maxDistance) {
                double kms;
                try {
                    calc.setDestinationGeographicPoint(r.point.getX(), r.point.getY());
                    kms = calc.getOrthodromicDistance();
                } catch (ArithmeticException e) {
                    kms = r.distance;
                } catch (IllegalArgumentException e) {
                    kms = r.distance;
                }
                if (kms <= maxDistance) {
                    results.add(new Neighbor(r.id, kms));
                }
            }
        }
        Collections.sort(results);
        if (results.size() > maxNeighbors) {
            results = results.subList(0, maxNeighbors);
        }
        return results;
    }

    public Geometry cleanupGeometry(Geometry g) {
        if (!(g instanceof MultiPolygon)) {
            return g;
        }
        Geometry largest = null;
        double largestArea = -1;
        MultiPolygon mp = (MultiPolygon)g;
        for (int i = 0; i < mp.getNumGeometries(); i++) {
            Geometry g2 = mp.getGeometryN(i);
            double area = g2.getArea();
            if (area > largestArea) {
                largestArea = area;
                largest = g2;
            }
        }
        return largest;
    }

    private double distance(GeodeticCalculator calc, Geometry g1, Geometry g2) {
        try {
            if (useBorders) {
                g1 = cleanupGeometry(g1);
                g2 = cleanupGeometry(g2);
                Coordinate[] pair = DistanceOp.nearestPoints(g1, g2);
                calc.setStartingGeographicPoint(pair[0].x, pair[0].y);
                calc.setDestinationGeographicPoint(pair[1].x, pair[1].y);
            } else {
                Point p1 = WikiBrainSpatialUtils.getCenter(g1);
                Point p2 = WikiBrainSpatialUtils.getCenter(g2);
                calc.setStartingGeographicPoint(p1.getX(), p1.getY());
                calc.setDestinationGeographicPoint(p2.getX(), p2.getY());
            }
            return calc.getOrthodromicDistance();
        } catch (ArithmeticException e) {
            Point p1 = WikiBrainSpatialUtils.getCenter(g1);
            Point p2 = WikiBrainSpatialUtils.getCenter(g2);
            return WikiBrainSpatialUtils.haversine(p1.getX(), p1.getY(), p2.getX(), p2.getY());
        } catch (IllegalArgumentException e) {
            Point p1 = WikiBrainSpatialUtils.getCenter(g1);
            Point p2 = WikiBrainSpatialUtils.getCenter(g2);
            return WikiBrainSpatialUtils.haversine(p1.getX(), p1.getY(), p2.getX(), p2.getY());
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy