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;