Как да изчислим ефективно най-близките 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
Не, само препратка към wiki, която описва алгоритъма. 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) в най-лошия случай, но е стабилно сортиране. Не съм сигурен дали стабилността е изискване за разделяй и владей. Сортирането по радикс е O(n log n) в най-добрия случай и O(kN) в най-лошия случай, може да се направи стабилно сортиране. Това е информация, която получих от wikipedia за тях.   -  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

Опцията post-data трябва да бъде поставена в кавички правилно и да съдържа urlencoded данни, т.е.

--post-data="%2Fhome%2Fsite%2Fadmin%2Fbrowse%2Fexec.done=checked"

Вижте също тази публикация.

  -  person Xotic750    schedule 08.03.2014


Отговори (2)


Така че, ако започвате с този списък от точки, рисуването на малка ограничителна кутия няма да намали много, защото все пак правите O(n) проверка спрямо всички точки за тяхното местоположение.

Бих посъветвал да използвате купчина с максимална дължина или някаква друга форма на частично сортиране, докато итерирате през всички точки. Това ви позволява да следите малко подмножество от приблизително максимални/минимални точки (както е описано от дължината), така че можете да ги изобразите бързо, преди да се занимавате с останалите. Ако имате нужда от повече обяснения за това, което точно казвам, уведомете ме.

Освен това за какво правите това, което има толкова строги проблеми с производителността? Обикновено изчисление като това не трябва да бъде точка на стрес, освен че имате 100k+ точки. Манипулирането на 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;
}));

Ето производителността за него в сравнение с груба сила

Heap код

js fiddle

Използвайки метода, който описах, и предварително изградена купчина, написана от друг човек, сравних нашите методи. Мисля, че ще бъдете щастливи! Той изпълни 8586 операции в секунда в сравнение с 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 и Haversine formula

Всъщност земята е много леко елипсоидална; използването на сферичен модел дава грешки обикновено до 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 и би причинило много объркване за други разработчици, напр.: [ 100, 95, distance: 19025.000000000004 ] - person Jake.JS; 15.02.2020