PHP и SQL: Изчисляване или запитване на голямо кръгово разстояние между точките на географска ширина и дължина с формулата на Haversine

Формула Haversine - Изчислете разстоянието на големия кръг с PHP или MySQL

Този месец програмирам доста на PHP и MySQL по отношение на ГИС. Плъзгайки се из мрежата, всъщност трудно намерих някои от Географски изчисления за да намеря разстоянието между две места, така че исках да ги споделя тук.

Карта на полета Европа с голямо кръгово разстояние

Най-простият начин за изчисляване на разстоянието между две точки е използването на питагоровата формула за изчисляване на хипотенузата на триъгълник (A² + B² = C²). Това е известно като Евклидово разстояние.

Това е интересно начало, но не важи за географията, тъй като разстоянието между линиите на географска ширина и дължина са не е равно разстояние на части. Когато се приближавате до екватора, географските ширини се отдалечават все повече. Ако използвате някакво просто уравнение за триангулация, то може да измери разстоянието точно на едно място и ужасно погрешно на другото, поради кривината на Земята.

Голям кръг Разстояние

Маршрутите, които се изминават на дълги разстояния около Земята, са известни като Голям кръг Разстояние. Тоест ... най-краткото разстояние между две точки на сфера е различно от точките в плоска карта. Комбинирайте това с факта, че линиите на географска ширина и дължина не са на еднакво разстояние ... и имате трудно изчисление.

Ето едно фантастично видео обяснение как работят Великите кръгове.

Формулата на Haversine

Разстоянието, използващо кривината на Земята, е включено в Формула на хаверзин, който използва тригонометрия, за да позволи кривината на земята. Когато намирате разстоянието между 2 места на земята (докато лети врана), една права линия е наистина дъга.

Това е приложимо при въздушен полет - гледали ли сте някога действителната карта на полетите и сте забелязали, че са извити? Това е така, защото е по-кратко да се лети в арка между две точки, отколкото директно до местоположението.

PHP: Изчислете разстоянието между 2 точки на географска ширина и дължина

Както и да е, ето PHP формулата за изчисляване на разстоянието между две точки (заедно с преобразуването на Mile срещу километър), закръглено до два знака след десетичната запетая.

function getDistanceBetweenPointsNew($latitude1, $longitude1, $latitude2, $longitude2, $unit = 'miles') {
  $theta = $longitude1 - $longitude2; 
  $distance = (sin(deg2rad($latitude1)) * sin(deg2rad($latitude2))) + (cos(deg2rad($latitude1)) * cos(deg2rad($latitude2)) * cos(deg2rad($theta))); 
  $distance = acos($distance); 
  $distance = rad2deg($distance); 
  $distance = $distance * 60 * 1.1515; 
  switch($unit) { 
    case 'miles': 
      break; 
    case 'kilometers' : 
      $distance = $distance * 1.609344; 
  } 
  return (round($distance,2)); 
}

SQL: Извличане на всички записи в диапазон чрез изчисляване на разстоянието в мили, използвайки географска ширина и дължина

Възможно е също така да използвате SQL, за да направите изчисление, за да намерите всички записи на определено разстояние. В този пример ще запитам MyTable в MySQL, за да намеря всички записи, които са по-малки или равни на променлива $ distance (в мили) до моето местоположение на $ latitude и $ longitude:

Заявката за извличане на всички записи в рамките на конкретна разстояние чрез изчисляване на разстоянието в мили между две точки на географска ширина и дължина са:

$query = "SELECT *, (((acos(sin((".$latitude."*pi()/180)) * sin((`latitude`*pi()/180)) + cos((".$latitude."*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((".$longitude."- `longitude`)*pi()/180)))) * 180/pi()) * 60 * 1.1515) as distance FROM `table` WHERE distance <= ".$distance."

Ще трябва да персонализирате това:

  • $ дължина - това е PHP променлива, където предавам географската дължина на точката.
  • $ ширина - това е PHP променлива, където предавам географската дължина на точката.
  • $ разстояние - това е разстоянието, на което искате да намерите всички записи, по-малки или равни.
  • маса - това е таблицата ... ще искате да замените това с името на вашата таблица.
  • ширина - това е полето на вашата географска ширина.
  • дължина - това е полето на вашата дължина.

SQL: Извличане на всички записи в диапазон чрез изчисляване на разстоянието в километри с помощта на географска ширина и дължина

И ето SQL заявката, използваща километри в MySQL:

$query = "SELECT *, (((acos(sin((".$latitude."*pi()/180)) * sin((`latitude`*pi()/180)) + cos((".$latitude."*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((".$longitude."- `longitude`) * pi()/180)))) * 180/pi()) * 60 * 1.1515 * 1.609344) as distance FROM `table` WHERE distance <= ".$distance."

Ще трябва да персонализирате това:

  • $ дължина - това е PHP променлива, където предавам географската дължина на точката.
  • $ ширина - това е PHP променлива, където предавам географската дължина на точката.
  • $ разстояние - това е разстоянието, на което искате да намерите всички записи, по-малки или равни.
  • маса - това е таблицата ... ще искате да замените това с името на вашата таблица.
  • ширина - това е полето на вашата географска ширина.
  • дължина - това е полето на вашата дължина.

Използвах този код в платформа за картографиране на предприятия, която използвахме за магазин за търговия на дребно с над 1,000 места в Северна Америка и той работи прекрасно.

77 Коментари

  1. 1

    Благодаря ви много за споделянето. Това беше лесна работа за копиране и поставяне и работи чудесно. Спестихте ми много време.
    FYI за всеки, който пренася на C:
    double deg2rad (double deg) {return deg * (3.14159265358979323846 / 180.0); }

  2. 2

    Много хубава публикация - работи много добре - трябваше само да променя името на масата, която държи дългата лат. Работи доста бързо за .. Имам сравнително малък брой лат-дълги (<400), но мисля, че това би се мащабирало добре. Хубав сайт също - току-що го добавих към моя акаунт в del.icio.us и ще проверявам редовно.

  3. 4
  4. 5

    Търсих целия ден за изчисления на разстоянието и намерих харверсиновия алгоритъм, благодарение на вас, че дадохте примера как да го поставите в sql изявление. Благодаря и поздрави, Даниел

  5. 8

    мисля, че вашият SQL се нуждае от изявление.
    вместо WHERE разстояние <= $ разстояние, което може да се наложи
    използвайте HAVING distance <= $ distance

    иначе благодаря, че ми спестихте куп време и енергия.

  6. 10
  7. 11
  8. 12

    Благодаря много, че споделихте този код. Спести ми много време за разработка. Също така, благодаря на вашите читатели, че посочиха, че за MySQL 5.x е необходимо изявление HAVING. Много полезно.

  9. 14

    Горната формула ми спестява много време. Благодаря ти много.
    Също така трябва да превключвам между NMEA формата и градусите. Намерих формула на този URL в долната част на страницата. http://www.errorforum.com/knowledge-base/16273-converting-nmea-sentence-latitude-longitude-decimal-degrees.html

    Някой знае ли как да провери това?

    Благодаря ви!
    тормозя

  10. 15

    Здравейте,

    Друг въпрос. Има ли формула за NMEA низове като тази по-долу?

    1342.7500, N, 10052.2287, Е

    $GPRMC,032731.000,A,1342.7500,N,10052.2287,E,0.40,106.01,101106,,*0B

    Благодаря,
    тормозя

  11. 16

    Също така установих, че WHERE не ми работи. Промених го на HAVING и всичко работи перфектно. Отначало не прочетох коментарите и ги пренаписах с помощта на вложен избор. И двете ще работят добре.

  12. 17

    Благодаря ви много за скрипта, написан в mysql, просто трябваше да направя няколко незначителни корекции (HAVING) 🙂
    Голяма работа

  13. 18

    Невероятно полезно, много благодаря! Имах някои проблеми с новия „HAVING“, а не „Where“, но след като прочетох коментарите тук (след около половин час скърцане на зъбите си разочаровано = P), го накарах да работи добре. Благодаря ви ^ _ ^

  14. 19
  15. 20

    Имайте предвид, че подобно твърдение като това ще бъде много изчислително интензивно и следователно бавно. Ако имате много от тези заявки, това може да забърка нещата доста бързо.

    Много по-малко интензивен подход е да се изпълни първа (сурова) селекция, като се използва КВАДРАТ област, дефинирана от изчислено разстояние, т.е. „изберете * от име на таблица, където географска ширина между lat1 и lat2 и дължина между lon1 и lon2“. lat1 = targetlatitude - latdiff, lat2 = targetlatitude + latdiff, подобно на lon. latdiff ~ = разстояние / 111 (за км) или разстояние / 69 за мили, тъй като 1 градус географска ширина е ~ 111 км (леко отклонение, тъй като земята е леко овална, но достатъчна за тази цел). londiff = разстояние / (abs (cos (deg2rad (географска ширина)) * 111)) - или 69 за мили (всъщност можете да вземете малко по-голям квадрат, за да отчетете вариациите). След това вземете резултата от това и го подайте в радиалния избор. Просто не забравяйте да вземете предвид извънграничните координати - т.е. диапазонът на приемлива дължина е -180 до +180 и диапазонът на приемлива географска ширина е -90 до +90 - в случай, че вашият латдиф или лондиф работи извън този диапазон . Имайте предвид, че в повечето случаи това може да не е приложимо, тъй като засяга изчисленията само по линия през Тихия океан от полюс до полюс, въпреки че пресича част от Чукотка и част от Аляска.

    Това, което постигаме с това, е значително намаляване на броя точки, спрямо които правите това изчисление. Ако имате милион глобални точки в базата данни, разпределени приблизително равномерно и искате да търсите в рамките на 100 км, тогава първото ви (бързо) търсене е с площ 10000 кв. Км и вероятно ще даде около 20 резултата (въз основа на равномерно разпределение върху площ от около 500M кв. км), което означава, че изпълнявате сложното изчисление на разстоянието 20 пъти за тази заявка, вместо милион пъти.

    • 21

      Незначителна грешка в примера ..., която би била в рамките на 50 км (а не 100), тъй като гледаме „радиуса“ на нашия ... квадрат.

      • 22

        Фантастичен съвет! Всъщност работех с разработчик, който написа функция, която издърпа вътрешния квадрат и след това рекурсивна функция, която направи "квадрати" по периметъра, за да включи и изключи останалите точки. Резултатът беше невероятно бърз - той можеше да оцени милиони точки за микросекунди.

        Моят подход по-горе определено е „груб“, но способен. Благодаря отново!

        • 23

          Дъг,

          Опитвам се да използвам mysql и php, за да преценя дали lat long point е в полигона. Знаете ли дали вашият приятел разработчик е публикувал примери за това как да се изпълни тази задача. Или знаете някакви добри примери. Благодаря предварително.

  16. 24

    Здравейте всички, това е моят тестов SQL израз:

    SELECT DISTINCT area_id, (
    (
    (
    acos( sin( ( 13.65 * pi( ) /180 ) ) * sin( (
    `lat_dec` * pi( ) /180 ) ) + cos( ( 13.65 * pi( ) /180 ) ) * cos( (
    `lat_dec` * pi( ) /180 )
    ) * cos( (
    ( 51.02 - `lon_dec` ) * pi( ) /180 )
    )
    )
    ) *180 / pi( )
    ) *60 * 1.1515 * 1.609344
    ) AS distance
    FROM `post_codes` WHERE distance <= 50

    и Mysql ми казва, че разстоянието не съществува като колона, мога да използвам подреждане по, мога да го направя без КЪДЕ и работи, но не и с него ...

  17. 26

    Това е чудесно, но точно както птиците летят. Би било чудесно да опитате да включите API на Google Maps по някакъв начин (може би с помощта на пътища и т.н.) Само за да дадете идея, използвайки различна форма на транспорт. Все още не съм направил симулирана функция за отгряване в PHP, която би могла да предложи ефективно решение на проблема с пътуващия продавач. Но мисля, че може да успея да използвам част от вашия код, за да го направя.

  18. 27

    Здравей Дъглас,
    благодаря ви много за тази статия - просто ми спестихте много време.
    пази се,
    Нимрод @ Израел

  19. 28

    Хубава статия! Намерих много статии, описващи как да се изчисли разстоянието между две точки, но наистина търсех SQL фрагмента.

  20. 29
  21. 30
  22. 31
  23. 32
  24. 36

    2 дни проучване, за да намеря най-накрая тази страница, която решава проблема ми. Изглежда, че е по-добре да унищожа WolframAlpha и да разбера математиката си. Промяната от WHERE на HAVING има моят скрипт в изправност. БЛАГОДАРЯ ТИ

  25. 37
    • 38

      Благодаря Георги. Продължавах да получавам колона „разстояние“ не беше намерена. След като сменям КЪДЕ на ХАВИНГ, това работи като чар!

  26. 39

    Иска ми се това да е първата страница, която бях намерил за това. След като изпробвах много различни команди, това беше единствената, която работеше правилно и с минимални промени, необходими за моята собствена база данни.
    Благодаря много!

  27. 40

    Иска ми се това да е първата страница, която бях намерил за това. След като изпробвах много различни команди, това беше единствената, която работеше правилно и с минимални промени, необходими за моята собствена база данни.
    Благодаря много!

  28. 41
  29. 42
  30. 43
  31. 45
  32. 46
  33. 47

    Знам, че тази формула работи, но не виждам къде се взема предвид радиусът на земята. Може ли някой да ме просветли, моля?

  34. 49
  35. 50

    Страхотни неща Дъглас. Опитвали ли сте да получите точката на пресичане, като се има предвид дължина / ширина / лагер на две точки?

  36. 52

    Благодаря ти Дъглас, SQL заявката е точно това, от което се нуждаех, и мислех, че ще трябва да я напиша сам. Спасихте ме от евентуално часове на кривата на географска ширина!

  37. 53
  38. 55
  39. 56

    Дъглас, благодаря ти за този невероятен код. Разбивах главата си за това как да направя това на моя портал на GPS общността. Спестихте ми часове.

  40. 58

    благодаря за публикуването на тази полезна статия,  
    но по някаква причина бих искал да попитам
    как да получа разстоянието между кордовете в mysql db и акордите, вмъкнати в php от потребителя?
    за по-ясно опишете:
    1. потребителят трябва да вмъкне [id] за избор на определени данни от db и самите потребителски координати
    2. php файлът получава целевите данни (coords) с помощта на [id] и след това изчислява разстоянието между потребителя и целевата точка

    или просто можете да получите разстояние от кода по-долу?

    $ qry = “SELECT *, (((acos (sin ((“. $ latitude. ”* pi () / 180)) * sin ((` Latitude` * pi () / 180)) + cos ((„. $ latitude. ”* pi () / 180)) * cos ((` Latitude` * pi () / 180)) * cos (((„. $ longitude." - `Longitude`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) като разстояние ОТ `MyTable` WHERE разстояние> =". $ Разстояние. " >>>> мога ли да „извадя” разстоянието от тук?
    Благодаря отново,
    Тими С.

  41. 60

    добре, всичко, което опитах, не работи. Искам да кажа, това, което имам, работи, но разстоянията са далеч.

    Може ли някой да види какво не е наред с този код?

    if (isset ($ _ POST ['submitted'])) {$ z = $ _POST ['zipcode']; $ r = $ _POST ['радиус']; ехо „Резултати за“. $ z; $ sql = mysql_query (“SELECT DISTINCT m.zipcode, m.MktName, m.LocAddSt, m.LocAddCity, m.LocAddState, m.x1, m.y1, m.verified, z1.lat, z2.lon, z1. град, z1.state ОТ mrk m, zip z1, zip z2 КЪДЕ m.zipcode = z1.zipcode И z2.zipcode = $ z И (3963 * acos (отсече (sin (z2.lat / 57.2958) * sin (m y1 / 57.2958) + cos (z2.lat / 57.2958) * cos (m.y1 / 57.2958) * cos (m.x1 / 57.2958 - z2.lon / 57.2958), 8))) <= $ r ") или умрете (mysql_error ()); докато ($ row = mysql_fetch_array ($ sql)) {$ store1 = $ row ['MktName']. ""; $ store = $ row ['LocAddSt']. ""; $ store. = $ row ['LocAddCity']. ",". $ row ['LocAddState']. " ". $ Row ['zipcode']; $ latitude1 = $ row ['lat']; $ longitude1 = $ row ['lon']; $ latitude2 = $ row ['y1']; $ longitude2 = $ row ['x1']; $ city = $ row ['city']; $ state = $ row ['state']; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = разстояние ($ lat1, $ lon1, $ lat2, $ lon2); $ проверен = $ ред ['проверен']; ако ($ проверено == '1') {ехо “”; echo “”. $ store. ””; echo $ dis. ”Миля (и) далеч”; ехо “”; } else {echo “”. $ store. ””; echo $ dis. ”Миля (и) далеч”; ехо “”; }}}

    моите функции.php код
    функция getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi') {$ theta = $ longitude1 - $ longitude2; $ разстояние = (sin (deg2rad ($ latitude1)) * sin (deg2rad ($ latitude2))) + (cos (deg2rad ($ latitude1)) * cos (deg2rad ($ latitude2)) * cos (deg2rad ($ theta)) ); $ разстояние = acos ($ разстояние); $ разстояние = rad2deg ($ разстояние); $ разстояние = $ разстояние * 60 * 1.1515; превключвател ($ единица) {случай 'Mi': прекъсване; дело 'Km': $ разстояние = $ разстояние * 1.609344; } return (кръг ($ разстояние, 2)); }

    Благодаря ви предварително

  42. 61
  43. 62

    Хей Дъглас, страхотна статия. Намерих вашето обяснение на географските понятия и кода наистина интересно. Единственото ми предложение би било да поставите интервал и да отстъпите кода за показване (като Stackoverflow например). Разбирам, че искате да спестите място, но конвенционалните разстояния / отстъпи на кодове биха улеснили много мен, като програмист, да чета и дисектирам. Във всеки случай това е малко нещо. Продължавайте със страхотната работа.

    • 63

      Благодаря! Модифицирах публикацията малко ... но уравненията заемат толкова много място и са толкова дълги, че не съм сигурен, че помага твърде много.

  44. 64
  45. 65

    тук, докато използваме с функция, получаваме един тип разстояние ... докато използваме заявка за идващия друг вид разстояние

  46. 66
  47. 67
  48. 68
  49. 69
  50. 70

    изглежда по-бързо (mysql 5.9) да се използва два пъти формулата в select и къде:
    $ формула = “(((acos (sin ((„. $ latitude. ”* pi () / 180)) * sin ((` Latitude` * pi () / 180)) + cos ((„. $ latitude. ”* Pi () / 180)) * cos ((` Latitude` * pi () / 180)) * cos (((„. $ Longitude." - "Longitude`) * pi () / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) “;
    $ sql = 'ИЗБЕРИ *,'. $ формула. ' като разстояние ОТ таблица КЪДЕ '.. $ формула.' <= '. $ разстояние;

  51. 71
  52. 72

    Благодаря много за срязването на тази статия. Това е много полезно.
    Първоначално PHP е създаден като проста сценарийна платформа, наречена „Лична начална страница“. В днешно време PHP (съкращение от Hypertext Preprocessor) е алтернатива на технологията Active Server Pages (ASP) на Microsoft.

    PHP е език с отворен код от страна на сървъра, който се използва за създаване на динамични уеб страници. Може да се вгради в HTML. PHP обикновено се използва заедно с MySQL база данни на Linux / UNIX уеб сървъри. Това е може би най-популярният скриптов език.

  53. 73

    Открих, че горното решение не работи правилно.
    Трябва да премина към:

    $ qqq = „ИЗБЕРЕТЕ *, (((acos (sin ((„. $ latitude. ”* pi () / 180)) * sin ((„ latt` * pi () / 180)) + cos ((“. $ географска ширина. “* pi () / 180)) * cos ((` latt` * pi () / 180)) * cos (((“. $ longitude.“ - `longt`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515) като разстояние ОТ `регистър`“;

  54. 75

    благодаря ви сър съркинг перфектно .. но имам един въпрос, ако искам да извеждам без десетична запетая тогава какво мога да направя ..?

    Благодаря предварително.

  55. 76

    Здравейте, моля наистина ще се нуждая от вашата помощ за това.

    Направих заявка за получаване на моя уеб-сървър http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ географска ширина
    -2.23389 = дължина $
    и 20 = разстоянието, което искам да извлека

    Въпреки това, използвайки формулата ви, тя извлича всички редове в моя db

    $ резултати = DB :: select (DB :: raw (“SELECT *, (((acos (sin ((.. latitude.” * pi () / 180)) * sin ((lat * pi () / 180 )) + cos ((„. $ latitude.” * pi () / 180)) * cos ((lat * pi () / 180)) * cos (((„. $ longitude.“ - lng) * pi ( ) / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) като разстояние ОТ маркери, ИМАЩИ разстояние> = “. $ Разстояние));

    [{„Id“: 1, „name“: „Frankie Johnnie & Luigo Too“, „address“: „939 W El Camino Real, Mountain View, CA“, „lat“: 37.386337280273, „lng“: - 122.08582305908, ”Distance”: 16079.294719663}, {“id”: 2, ”name”: ”Пицария на Източното крайбрежие на Амичи”, ”address”: ”790 Castro St, Mountain View, CA”, ”lat”: 37.387138366699, ”lng”: -122.08323669434, "разстояние": 16079.175940152}, {"id": 3, "name": "Pizza Bar & Grill на Kapp's", "address": "191 Castro St, Mountain View, CA", "lat": 37.393886566162, ”Lng”: - 122.07891845703, “разстояние”: 16078.381373826}, {“id”: 4, ”name”: ”Пица с кръгла маса: Mountain View”, ”address”: ”570 N Shoreline Blvd, Mountain View, CA”, ”Lat”: 37.402652740479, “lng”: - 122.07935333252, “distance”: 16077.420540582}, {“id”: 5, ”name”: ”Пица и паста на Тони и Алба”, ”address”: ”619 Escuela Ave, Mountain Изглед, Калифорния "," lat ": 37.394012451172," lng ": - 122.09552764893," разстояние ": 16078.563225154}, {" id ": 6," name ":" Пица с дърва на Орегано "," адрес ":" 4546 El Camino Real, Los Altos, CA ”,” lat ”: 37.401725769043,” lng ”: - 122.11464691162,“ distance ”: 16077.937560795}, {“ id ”: 7,” name ”:” The bars and grills ”,” address ”:” 24 Whiteley Street, Manchester ”,” lat ”: 53.485118865967,” lng ”: - 2.1828699111938,” distance ”: 8038.7620112314}]

    Искам да извлека само редове с 20 мили, но това води до всички редове. Моля, какво правя грешно

  56. 77

    Търся подобна заявка, но се засилих малко - накратко това е да групирам всички координати в рамките на 2 мили от всяка координата и след това да преброя колко координати във всяка група и да изведе само една група, която има най -много координати - дори ако имате повече от една група сред групи с най -голям брой координати - просто изведете произволната група от групите със същия най -голям брой -

Какво мислите?

Този сайт използва Akismet за намаляване на спама. Научете как се обработват данните за коментарите ви.