Как эффективно рассчитать ближайшие 2D-точки в JavaScript?

У меня есть набор местоположений, которые я хочу отобразить для пользователя в порядке близости - от ближайшего к самому дальнему - на основе их текущих координат. Предположим, у нас есть ~ 100 точек данных местоположений, которые включают широту и долготу каждого (в каком-то объекте или массиве), и мы знаем широту и долготу пользователя. Цель состоит в том, чтобы отобразить упорядоченный список местоположений - и было бы полезно, если бы мы могли получить и отобразить пользователю ближайшие 8-10 местоположений, пока мы вычисляем, а затем отображаем оставшиеся расстояния.

Я знаю, что решение методом грубой силы состоит в том, чтобы перебрать все местоположения, вычислить расстояние от пользователя, расположить их по порядку, а затем отобразить их все для пользователя. Но это слишком медленно.

Лучшее решение: https://stackoverflow.com/a/2466908/1766230, где вы проверяете в ограниченном сначала коробку, при необходимости расширяя, а потом сделайте все остальное.

Я также видел, что существуют другие алгоритмы, такие как FLANN и другие методы - но я не видел примеров, написанных на JavaScript.

Итак, вопрос: каков самый быстрый способ вычислить (и отобразить по порядку) ближайшие точки в JavaScript?


person Luke    schedule 06.03.2014    source источник
comment
С ~ 100 баллами маловероятно, что вы увидите много преимуществ метода разделяй и властвуй над наивным методом грубой силы. Наивный метод очень прост в реализации.   -  person Xotic750    schedule 07.03.2014
comment
На самом деле это для мобильного приложения, использующего Titanium. Придется покопаться, чтобы узнать почему сейчас это узкое место ... но, тем не менее, я хотел бы найти оптимальное решение, если оно не слишком сложно, а не просто грубо- заставить это.   -  person Luke    schedule 07.03.2014
comment
Насколько я понимаю, метод «разделяй и властвуй» является оптимальным O (n log n) - в зависимости от алгоритма сортировки (я думаю, требуется сортировка слиянием)   -  person Xotic750    schedule 07.03.2014
comment
Есть ли у вас какие-нибудь примеры использования JavaScript «разделяй и властвуй»?   -  person Luke    schedule 07.03.2014
comment
Нет, только вики-ссылка, описывающая алгоритм. en.wikipedia.org/wiki/   -  person Xotic750    schedule 07.03.2014
comment
Для простой сортировки выберите быструю сортировку или сортировку по основанию. Оба будут быстрее, чем сортировка слиянием!   -  person Brent Echols    schedule 07.03.2014
comment
quicksort - это O (n log n) в лучшем случае, O (n2) в худшем случае, но не является стабильной сортировкой, mergesort - O (n log n) в худшем случае, но является стабильной сортировкой. Неуверен, является ли стабильность требованием принципа «разделяй и властвуй». Сортировка Radix - это O (n log n) в лучшем случае и O (kN) в худшем случае, ее можно сделать стабильной. Это информация, которую я получил о них из википедии.   -  person Xotic750    schedule 07.03.2014
comment
Можете ли вы показать нам свою рутину грубой силы?   -  person Xotic750    schedule 07.03.2014
comment
есть ли у вас возможность предварительно обработать местоположения, прежде чем вам сообщат местоположение пользователя? Или местоположения поступают из какого-то внешнего источника или из процесса, включающего местоположение пользователя / пользователя?   -  person גלעד ברקן    schedule 08.03.2014
comment
@ גלעדברקן Это очень хороший аргумент! Я думал о проблеме, поскольку ~ 100 точек и текущее местоположение ранее неизвестны.   -  person Xotic750    schedule 08.03.2014
comment
Еще я не учел, что земля не плоская! :П   -  person Xotic750    schedule 08.03.2014


Ответы (2)


Итак, если вы начинаете с этого списка точек, рисование небольшой ограничивающей рамки не сильно урежет, потому что вы все равно выполняете проверку O (n) для всех точек на предмет их местоположения.

Я бы посоветовал использовать кучу максимальной длины или какую-либо другую форму частичной сортировки при повторении всех точек. Это позволяет вам отслеживать небольшое подмножество приблизительно максимальных / минимальных точек (как описано длиной), чтобы вы могли быстро отрендерить их, прежде чем разбираться с остальными. Если вам нужно больше пояснений по поводу того, что я говорю, дайте мне знать.

И что вы делаете из-за таких серьезных проблем с производительностью? Обычно такие вычисления не должны вызывать стресса, за исключением того, что у вас есть более 100 тысяч баллов. Манипуляции с DOM - обычно самое дорогое место

var points = [];
for (i = 0; i < 100; i++) {
    var point = [getRandomInt(0, 999), getRandomInt(0, 999)];
    point.len = distanceBetweenPoints(point, [499,499]);
    points.push(point);
}

console.log(Heap.nsmallest(points, 10, function (a, b) {
  return a.len < b.len;
}));

Вот его производительность по сравнению с брутфорс

Код кучи

js fiddle

Используя описанный мной метод и предварительно созданную кучу, написанную другим человеком, я сравнил наши методы. Думаю, будет тебе счастье! Он выполнял 8 586 операций в секунду по сравнению с 566 при использовании техники грубой силы!

person Brent Echols    schedule 06.03.2014
comment
Не могли бы вы подробнее рассказать о решении кучи максимальной длины? Разве для этого не потребовалось бы перебрать все места, прежде чем отобразить 8–10 ближайших? - person Luke; 07.03.2014
comment
Таким образом, невозможно обойтись без поиска по x точкам, если только у вас нет сервера, который позаботится о некоторой фильтрации за вас. Однако вместо сортировки «n» терминов вы сохраняете кучу, скажем, длины 10. Максимальный элемент находится наверху, когда вы проверяете другое значение, вы сравниваете его с этим. Если ваш ток меньше максимального, добавьте его в кучу и (если он заполнен) сдвиньте верх. Если он больше, просто перейдите к следующему элементу. По сути, вы не можете НЕ проверять все из них, так что это должен быть хороший способ своевременно получить 10 лучших элементов! - person Brent Echols; 07.03.2014
comment
Технически это примерно тот же самый худший случай, что и ведение отсортированного списка, но в среднем должно быть значительно лучше, особенно. @ большие значения. Надеюсь, это все объясняет более подробно! Дайте мне знать, если я могу помочь: D - person Brent Echols; 07.03.2014
comment
Спасибо, думаю, теперь я это понимаю. - person Luke; 07.03.2014
comment
Привет! Я обновил все и добавил код, если вам было интересно! - person Brent Echols; 08.03.2014

Это моя попытка отсортировать массив точек по расстоянию до заданной точки. Насколько я понимаю, это перебор. Затем я slice массив, чтобы дать вам 10 ближайших точек.

JavaScript

function distanceBetweenPoints(p1, p2) {
    return Math.abs(Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1])));
}

function sortByDistance(location, arrayOfPoints) {
    arrayOfPoints.sort(function (a, b) {
        a.distance = distanceBetweenPoints(location, a);
        b.distance = distanceBetweenPoints(location, b);

        return a.distance - b.distance;
    });

    return arrayOfPoints;
}

function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

var points = [];

for (i = 0; i < 100; i += 1) {
    points.push([getRandomInt(-90, 90), getRandomInt(-180, 180)]);
}

console.log(sortByDistance([0, 0], points).slice(0, 10));

На jsFiddle

Это, по крайней мере, даст вам возможность протестировать алгоритмы. А вот jsPerf для вышеупомянутого, так что вы можете добавить к нему другие подпрограммы и провести некоторые реальные сравнения производительности. .

Примечание: при этом не учитывается, что Земля - ​​сфера! Это вычисляет Euclidean distance, а не геодезическое расстояние. Это нормально, если точки находятся, например, в одном городе (или в непосредственной близости), но не в том случае, если они находятся в разных странах / континентах. Также предполагается, что вы преобразовали долготу и широту в десятичное представление.

В противном случае вам нужно будет посмотреть на такие вещи, как Great-circle distance и _ 5_

Фактически, Земля имеет очень немного эллипсовидную форму; использование сферической модели дает обычно ошибки до 0,3%

JavaScript

function toRadians(degrees) {
    return (degrees * Math.PI) / 180;
}

// Haversine formula
function distanceBetweenPoints(p1, p2) {
    var R = 6371, // mean earth radius in km
        lat1 = toRadians(p1[0]),
        lon1 = toRadians(p1[1]),
        lat2 = toRadians(p2[0]),
        lon2 = toRadians(p2[1]),
        dLat = lat2 - lat1,
        dLon = lon2 - lon1,
        a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2),
        c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)),
        d = R * c;

    return d;
}

function sortByDistance(location, arrayOfPoints) {
    arrayOfPoints.sort(function (a, b) {
        a.distance = distanceBetweenPoints(location, a);
        b.distance = distanceBetweenPoints(location, b);

        return a.distance - b.distance;
    });

    return arrayOfPoints;
}

function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

var points = [];

for (i = 0; i < 100; i += 1) {
    points.push([getRandomInt(-90, 90), getRandomInt(-180, 180)]);
}

console.log(sortByDistance([0, 0], points).slice(0, 10));

На jsFiddle

person Xotic750    schedule 08.03.2014
comment
Отличный ответ, но небольшое предостережение @ Xotic750 добавление .distance в первый блок кода в arrayOfPoints.sort(...) к массиву не является лучшей практикой JS imo и вызовет много путаницы для других разработчиков ex: [ 100, 95, distance: 19025.000000000004 ] - person Jake.JS; 15.02.2020