19 package org.sleuthkit.autopsy.datasourcesummary.datamodel;
 
   21 import java.util.Collection;
 
   22 import java.util.List;
 
   24 import java.util.function.Function;
 
   25 import java.util.stream.Collectors;
 
   26 import java.util.stream.Stream;
 
   27 import org.apache.commons.lang3.tuple.Pair;
 
   35 class LatLngMap<E 
extends KdTree.XYZPoint> {
 
   38     private static final double EARTH_RADIUS = 6371;
 
   41     private static final double DEFAULT_BUCKET_SIZE = 300;
 
   44     private final Map<Pair<Integer, Integer>, KdTree<E>> latLngMap;
 
   46     private final double bucketSize;
 
   49     private final Function<XYZPoint, Pair<Integer, Integer>> bucketCalculator = (point) -> {
 
   50         Pair<Double, Double> dPair = getBucketLocation(point);
 
   51         return Pair.of((
int) (
double) dPair.getLeft(), (int) (
double) dPair.getRight());
 
   59     LatLngMap(List<E> pointsToAdd) {
 
   60         this(pointsToAdd, DEFAULT_BUCKET_SIZE);
 
   70     LatLngMap(List<E> pointsToAdd, 
double bucketSize) {
 
   71         this.bucketSize = bucketSize;
 
   73         Map<Pair<Integer, Integer>, List<E>> latLngBuckets = pointsToAdd.stream()
 
   74                 .collect(Collectors.groupingBy((pt) -> bucketCalculator.apply(pt)));
 
   76         this.latLngMap = latLngBuckets.entrySet().stream()
 
   77                 .map(e -> Pair.of(e.getKey(), 
new KdTree<E>(e.getValue())))
 
   78                 .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
 
   92     private Pair<Double, Double> getBucketLocation(XYZPoint point) {
 
   93         double y = euclideanDistance(
new XYZPoint(0D, 0D), 
new XYZPoint(0D, point.getY())) / bucketSize;
 
   94         if (point.getY() < 0) {
 
   98         double x = euclideanDistance(
new XYZPoint(0D, point.getY()), 
new XYZPoint(point.getX(), point.getY())) / bucketSize;
 
   99         if (point.getX() < 0) {
 
  103         return Pair.of(y, x);
 
  113     E findClosest(E point) {
 
  114         Pair<Double, Double> calculated = getBucketLocation(point);
 
  117         int latBucket = (int) (
double) calculated.getLeft();
 
  118         int latBucket2 = Math.round(calculated.getLeft()) == latBucket ? latBucket - 1 : latBucket + 1;
 
  120         int lngBucket = (int) (
double) calculated.getRight();
 
  121         int lngBucket2 = Math.round(calculated.getRight()) == lngBucket ? lngBucket - 1 : lngBucket + 1;
 
  123         E closest1 = findClosestInBucket(latBucket, lngBucket, point);
 
  124         E closest2 = findClosestInBucket(latBucket2, lngBucket, point);
 
  125         E closest3 = findClosestInBucket(latBucket, lngBucket2, point);
 
  126         E closest4 = findClosestInBucket(latBucket2, lngBucket2, point);
 
  128         return Stream.of(closest1, closest2, closest3, closest4)
 
  129                 .filter(c -> c != null && euclideanDistance(point, c) <= bucketSize / 2)
 
  130                 .min((a, b) -> Double.compare(euclideanDistance(point, a), euclideanDistance(point, b)))
 
  143     private E findClosestInBucket(
int x, 
int y, E point) {
 
  144         KdTree<E> thisLatLngMap = latLngMap.get(Pair.of(x, y));
 
  145         if (thisLatLngMap == null) {
 
  149         Collection<E> closest = thisLatLngMap.nearestNeighbourSearch(1, point);
 
  150         if (closest != null && closest.size() > 0) {
 
  151             return closest.iterator().next();
 
  168     private static double euclideanDistance(KdTree.XYZPoint o1, KdTree.XYZPoint o2) {
 
  173         double lat1Radians = Math.toRadians(o1.getY());
 
  174         double lat2Radians = Math.toRadians(o2.getY());
 
  176         double deltaLatRadians = Math.toRadians(o2.getY() - o1.getY());
 
  177         double deltaLongRadians = Math.toRadians(o2.getX() - o1.getX());
 
  179         double a = Math.sin(deltaLatRadians / 2) * Math.sin(deltaLatRadians / 2)
 
  180                 + Math.cos(lat1Radians) * Math.cos(lat2Radians)
 
  181                 * Math.sin(deltaLongRadians / 2) * Math.sin(deltaLongRadians / 2);
 
  183         double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
 
  185         return EARTH_RADIUS * c;