Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: SQL i sortowanie według odległości GPS
Forum PHP.pl > Forum > Bazy danych > MySQL
acztery
Witam,

Mam bazę 30 tys rekordów, każdy rekord ma dane geograficzne (szerokość i długość - dziesiętną) jak zrobić zapytanie które pokaże listę posortowanych rekordów według dystansu.



Czyli najpierw obliczamy dystans dla każdego rekordu na podstawie danych lokalizacyjnych. potem sortujemy.

żeby zobrazować.

mam listę kin w Polsce użytkownik normalnie ogląda listę posortowaną według daty dodana. Ale jak w swoich ustawieniach konta poda swoje dane geograficzne to na ich podstawie będzie mógł zobaczyć co jest najbliżej jego lokalizacji.

na razie wyklułem coś takiego:

  1. function m_listdatabase ($category)
  2. {
  3. return $this->MysqlGetArray("SELECT *, ROUND( ACOS( SIN( RADIANS( lat ) ) * SIN( 19.0237800 ) + COS( RADIANS( lat ) ) * COS( 50.2648900 ) * COS( RADIANS( lat ) - 50.2648900 ) ) * 6371.01, 2 ) AS distance FROM `database` WHERE act=1 AND cat = '".$this->sqlFiltr($category)."' GROUP by id ORDER by distance DESC LIMIT ".model_settings::m_get_value(34)." ");
  4. }



MUSI BYC TO ZAPYTANIE BEZ INGERENCJI PHP.
wookieb
Won do kursu!
Po konsultacji otwieram - źle opisany post

  1. ORDER BY distance DESC


Pomijam fakt, że twoje zapytanie jest niesamowicie niewydajne i lepiej ograniczyć zakres wyszukiwanych rekordów do pewnego obszaru, np 100 / 30 km od danego punktu
acztery
tak jest.. chodzi o to ze zmienna distance jest zła wartość - tam mam jakieś duże liczny.. Ograniczać nie mogę... na bazie 30 tys rekordów narazie zapytanie wykonuję sie mniej niż sek. wiec to nie problem.

aha żeby ograniczyć zakres i tak muszę wyliczyć dystans dla wszystkich wiec Twoja rada na temat jest bez sensu:)
wookieb
Nie musisz. Możesz wyliczyć w jakim obszarzę będą się znajdowały punkty (no niestety po stronie php albo w mysql też da radę) i dodać zakres przeszukiwań w WHERE. Nie wiem dlaczego masz takie dziwne wartości, dawno się tym nie bawiłem.
acztery
Cytat(acztery @ 12.09.2011, 13:22:40 ) *
tak jest.. chodzi o to ze zmienna distance jest zła wartość - tam mam jakieś duże liczny.. Ograniczać nie mogę... na bazie 30 tys rekordów narazie zapytanie wykonuję sie mniej niż sek. wiec to nie problem.

aha żeby ograniczyć zakres i tak muszę wyliczyć dystans dla wszystkich wiec Twoja rada na temat jest bez sensu:)


w php mam ładny skrypt.


  1. class OrthodromicDistance
  2. {
  3.  
  4. const EQUATORIAL_RADIUS = 6378.137; // km
  5. const POLAR_RADIUS = 6356.7523; // km
  6.  
  7. private static function sqr($x)
  8. {
  9. return $x * $x;
  10. }
  11.  
  12. private static function getRadiusAt($latitude)
  13. {
  14. $latitude = deg2rad($latitude);
  15.  
  16. return sqrt((self::sqr(self::sqr(self::EQUATORIAL_RADIUS) * cos($latitude)) + self::sqr(self::sqr(self::POLAR_RADIUS) * sin($latitude))) /
  17. (self::sqr(self::EQUATORIAL_RADIUS * cos($latitude)) + self::sqr(self::POLAR_RADIUS * sin($latitude))));
  18. }
  19.  
  20. public static function getDistanceByCoordinates($fromLatitude, $fromLongitude,
  21. $toLatitude, $toLongitude)
  22. {
  23. $fromLatitude = deg2rad($fromLatitude);
  24. $fromLongitude = deg2rad($fromLongitude);
  25. $toLatitude = deg2rad($toLatitude);
  26. $toLongitude = deg2rad($toLongitude);
  27.  
  28. $x = sqrt(self::sqr(cos($toLatitude) * sin($fromLongitude - $toLongitude)) + self::sqr(cos($fromLatitude) * sin($toLatitude) - sin($fromLatitude) * cos($toLatitude) * cos($fromLongitude - $toLongitude)));
  29. $y = sin($fromLatitude) * sin($toLatitude) + cos($fromLatitude) * cos($toLatitude) * cos($fromLongitude - $toLongitude);
  30.  
  31. $averageRadius = (self::getRadiusAt($fromLatitude) + self::getRadiusAt($toLatitude)) / 2;
  32.  
  33. return atan2($x, $y) * $averageRadius;
  34. }
  35. }


i ładnie działa, to samo trzeba zrobić w SQL.

zrobiłbym to też na php ale tu paginacja inaczej wygląda po jak sortujemy w w SQL to paginacja za pomocą LIMIT , a jak za pomocą php miałbym obliczać to pobieram wszystko to tablicy potem tem skryptem wyliczam. potem sortuje nową tablice i sortuje już inaczej.. a nie mam czasu na takie działania teraz.

erix
Uhm, a nie możesz tego zrobić najprościej, jak się da? Tzn. przez długość wektora pomiędzy punktami?
wookieb
Yyy to obliczanie uwzględnia bodajże nawet kulistość ziemi, ale głowy nie daję.
erix
No tak, tylko że na tak małej powierzchni można IMO sobie spokojnie to darować. [;
acztery
erix mogę ale wtedy mogą być przekłamania w wynikach. czyli jak mam np 3 kina obok siebie to te wyniki mogą być złe. Po za tym błędna różnica w odległości bedzie rosnąć wraz z odległością od punktu a do punktu B. Jak zrobie to pokażę.

Ps to co pokazałem uwzględnia to że ziemia jest okrągła.... (prawie okrągła bo koło to to nie jest ale to tak na marginesie)


Procedura ładnie działa.
  1. CREATE DEFINER=`nazwa_usera_bazy`@`%` FUNCTION `get_distance_metres`(lat1 DOUBLE, lng1 DOUBLE, lat2 DOUBLE, lng2 DOUBLE) RETURNS double
  2. BEGIN
  3. DECLARE rlo1 DOUBLE;
  4. DECLARE rla1 DOUBLE;
  5. DECLARE rlo2 DOUBLE;
  6. DECLARE rla2 DOUBLE;
  7. DECLARE dlo DOUBLE;
  8. DECLARE dla DOUBLE;
  9. DECLARE a DOUBLE;
  10.  
  11. SET rlo1 = RADIANS(lng1);
  12. SET rla1 = RADIANS(lat1);
  13. SET rlo2 = RADIANS(lng2);
  14. SET rla2 = RADIANS(lat2);
  15. SET dlo = (rlo2 - rlo1) / 2;
  16. SET dla = (rla2 - rla1) / 2;
  17. SET a = SIN(dla) * SIN(dla) + COS(rla1) * COS(rla2) * SIN(dlo) * SIN(dlo);
  18. RETURN (6378137 * 2 * ATAN2(SQRT(a), SQRT(1 - a)))/1000;
  19. END
maly_swd
( 6371 * acos( cos( radians({$dane['google_lat']}) ) * cos( radians(ob.google_lat ) ) * cos( radians(ob.google_lng ) - radians({$dane['google_lng']}) ) + sin( radians({$dane['google_lat']}) ) * sin( radians(ob.google_lat ) ) ) ) AS distance,


ja uzywam takiego czegos i liczy idealnie. W $dane['google_xxx'] podstawiasz od jakiego pkt ma szukac i w distance masz odleglosc jaka dzieli punkt z tym co masz w bazie
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.