PHP ו- SQL: חישוב או שאילתת מרחק מעגל גדול בין נקודות קו רוחב ואורך בעזרת נוסחת ההברסין

נוסחת Haversine - חישוב מרחק מעגל גדול באמצעות PHP או MySQL

החודש תכננתי לא מעט ב- PHP וב- MySQL ביחס ל- GIS. חיטטתי ברשת, התקשיתי למצוא חלק מה- חישובים גיאוגרפיים למצוא את המרחק בין שני מיקומים ולכן רציתי לשתף אותם כאן.

מפת טיסה אירופה עם מרחק מעגל גדול

הדרך הפשוטה לחישוב מרחק בין שתי נקודות היא באמצעות הנוסחה הפיתגוראית לחישוב ההיפוטנוזה של משולש (A² + B² = C²). זה ידוע בשם מרחק אוקלידי.

זו התחלה מעניינת, אך היא אינה חלה על גאוגרפיה מכיוון שהמרחק בין קווי רוחב ואורך הוא לא מרחק שווה מלבד. ככל שמתקרבים לקו המשווה, קווי רוחב מתרחקים זה מזה. אם אתה משתמש באיזושהי משוואת משולש פשוטה, היא עשויה למדוד מרחק במדויק במיקום אחד ושגויה מאוד במקום השני בגלל עקמומיות כדור הארץ.

מרחק מעגל גדול

המסלולים הנעים למרחקים ארוכים סביב כדור הארץ מכונים מרחק מעגל גדול. כלומר ... המרחק הקצר ביותר בין שתי נקודות בכדור שונה מהנקודות במפה שטוחה. שלבו את זה עם העובדה שקווי הרוחב והאורך אינם שווים ... וקיבלתם חישוב קשה.

הנה הסבר וידאו נהדר על האופן שבו המעגלים הגדולים עובדים.

פורמולת האברסין

המרחק באמצעות העקמומיות של כדור הארץ משולב ב נוסחת האברסין, המשתמשת בטריגונומטריה כדי לאפשר את עקמומיות כדור הארץ. כשאתה מוצא את המרחק בין שני מקומות על פני כדור הארץ (כשעוף העורב), קו ישר הוא באמת קשת.

זה אפשרי בטיסה אווירית - האם הסתכלת אי פעם במפת הטיסות בפועל ושמת לב שהם מקושתים? הסיבה לכך היא שקצר יותר לטוס בקשת בין שתי נקודות מאשר ישירות למיקום.

PHP: חישוב מרחק בין 2 נקודות רוחב ואורך

בכל מקרה, הנה נוסחת ה- PHP לחישוב המרחק בין שתי נקודות (יחד עם המרה מייל לעומת קילומטר) מעוגלת לשני מקומות עשרוניים.

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 כדי למצוא את כל הרשומות שקטן או שווה למרחק $ המשתנה (במייל) למיקום שלי ברוחב $ ו- 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 מיקומים ברחבי צפון אמריקה וזה עבד יפה.

תגובות 76

  1. 1

    תודה רבה על השיתוף. זו הייתה עבודת העתקה והדבקה קלה ועובדת מצוין. חסכת לי הרבה זמן.
    לידיעתך לכל מי שמעביר ל- C:
    deg2rad כפול (dego כפול) {return deg * (3.14159265358979323846 / 180.0); }

  2. 2

    חתיכת פרסום נחמדה מאוד - עבדה יפה מאוד - הייתי צריך לשנות את שם השולחן המחזיק את הלוח הארוך. זה עובד די מהר ל .. יש לי מספר קטן למדי של שפות ארוכות (<400) אבל אני חושב שזה יתמדה יפה. גם אתר נחמד - הרגע הוספתי אותו לחשבון del.icio.us שלי ואבדוק שוב בקביעות.

  3. 4
  4. 5
  5. 8
  6. 10
  7. 11
  8. 12

    תודה רבה ששיתפת את הקוד הזה. זה חסך לי הרבה זמן פיתוח. כמו כן, תודה לקוראים שלך שהצביעו כי הצהרת HAVING נחוצה עבור MySQL 5.x. עוזר מאוד.

  9. 14
  10. 15
  11. 16

    גיליתי גם ש- WHERE לא עובד בשבילי. שינה אותו ל- HAVING והכל עובד מושלם. בהתחלה לא קראתי את ההערות ושכתבתי אותן מחדש באמצעות בחירה מקוננת. שניהם יעבדו בסדר גמור.

  12. 17
  13. 18

    מועיל להפליא, תודה רבה! נתקלתי בבעיות עם ה- "HAVING" החדש, ולא עם "WHERE", אבל ברגע שקראתי את התגובות כאן (אחרי כחצי שעה של חריקת שיניים בתסכול = P), קיבלתי את זה עובד יפה. תודה לך ^ _ ^

  14. 19
  15. 20

    זכור כי אמירה נבחרת כזו תהיה אינטנסיבית מאוד מבחינה חישובית ולכן איטית. אם יש לך הרבה מהשאילתות האלה, זה יכול להכשיל דברים די מהר.

    גישה הרבה פחות אינטנסיבית היא להריץ בחירה ראשונה (גולמית) באמצעות אזור SQUARE המוגדר על ידי מרחק מחושב כלומר "בחר * משם הטבלה שבו קו הרוחב בין lat1 ל lat2 ואורך בין lon1 ל lon2". lat1 = targetlatitude - latdiff, lat2 = targetlatitude + latdiff, דומה עם לון. latdiff ~ = מרחק / 111 (לק"מ), או מרחק / 69 למיילים שכן דרגת רוחב אחת היא ~ 1 ק"מ (וריאציה קלה מכיוון שכדור הארץ סגלגל מעט, אך מספיק למטרה זו). לונדיף = מרחק / (abs (cos (deg111rad (קו רוחב)) * 2)) - או 111 למיילים (אתה יכול למעשה לקחת ריבוע קצת יותר גדול על מנת להסביר וריאציות). ואז קח את התוצאה של זה והאכיל אותה לבחירה הרדיאלית. רק אל תשכח להתחשב בקואורדינטות מחוץ לתחום - כלומר טווח קו האורך המקובל הוא -69 עד +180 וטווח קו הרוחב המקובל הוא -180 עד +90 - אם לטדיף או לונדיף שלך עוברים מחוץ לטווח זה . שים לב שברוב המקרים זה לא יכול להיות ישים מכיוון שהוא משפיע רק על חישובים על קו דרך האוקיאנוס השקט מקוטב לקוטב, אם כי הוא מצטלב חלק מצ'וקוטקה וחלק מאלסקה.

    מה שאנו משיגים בכך הוא צמצום משמעותי במספר הנקודות שכנגדן אתה מבצע חישוב זה. אם יש לך מיליון נקודות גלובליות במסד הנתונים המופץ באופן שווה בערך ואתה רוצה לחפש תוך 100 ק"מ, אז החיפוש הראשון (המהיר) שלך הוא בשטח 10000 מ"ר וככל הנראה יניב כ -20 תוצאות (בהתבסס על התפלגות שווה שטח פנים של כ -500 מיליון קמ"ר), מה שאומר שאתה מריץ את חישוב המרחק המורכב 20 פעמים לשאילתה זו במקום מיליון פעמים.

    • 21

      טעות קטנה בדוגמה ... זה יהיה בטווח של 50 ק"מ (לא 100) מכיוון שאנחנו מסתכלים על "הרדיוס" של ... הריבוע שלנו.

      • 22

        עצה נפלאה! למעשה עבדתי עם מפתח שכתב פונקציה שמשכה את הריבוע הפנימי ואז פונקציה רקורסיבית שעשתה 'ריבועים' סביב ההיקף כדי לכלול ולהוציא את הנקודות הנותרות. התוצאה הייתה תוצאה מהירה להפליא - הוא יכול היה להעריך מיליוני נקודות במיקרו-שניות.

        הגישה שלי לעיל בהחלט 'גסה' אך מסוגלת. שוב תודה!

        • 23

          דאג,

          ניסיתי להשתמש ב- mysql ו- php כדי להעריך האם נקודה ארוכה היא בתוך מצולע. האם אתה יודע אם חברך המפתח פרסם דוגמאות כיצד לבצע משימה זו. או שאתה מכיר דוגמאות טובות. תודה מראש.

  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 אומר לי שהמרחק, לא קיים כעמודה, אני יכול להשתמש בסדר לפי, אני יכול לעשות את זה בלי WHERE, וזה עובד, אבל לא איתו ...

  17. 26

    זה נהדר, אולם זה בדיוק כשהציפורים עפות. זה יהיה נהדר לנסות ולשלב את ה- API של google maps איכשהו (אולי באמצעות כבישים וכו ') רק כדי לתת רעיון באמצעות צורת תחבורה אחרת. עדיין לא ביצעתי פונקציית חישול מדומה ב- PHP שתוכל להציע פיתרון יעיל לבעיית איש המכירות הנוסע. אבל אני חושב שאולי אוכל לעשות שימוש חוזר בחלק מהקוד שלך לשם כך.

  18. 27
  19. 28

    מאמר טוב! מצאתי הרבה מאמרים המתארים כיצד לחשב מרחק בין שתי נקודות אבל באמת חיפשתי את קטע ה- SQL.

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

    יומיים של מחקר כדי למצוא סוף סוף את הדף הזה שפותר את הבעיה שלי. נראה שעדיף שאפרוס את WolframAlpha שלי ואצחצח את המתמטיקה שלי. השינוי מ- WHERE ל- HAVING התסריט שלי תקין. תודה

  25. 37
  26. 39

    הלוואי שזה היה העמוד הראשון שמצאתי בנושא. לאחר שניסיתי פקודות רבות ושונות זה היה היחיד שעבד כראוי, ועם שינויים מינימליים הדרושים כדי להתאים למסד הנתונים שלי.
    תודה רבה!

  27. 40

    הלוואי שזה היה העמוד הראשון שמצאתי בנושא. לאחר שניסיתי פקודות רבות ושונות זה היה היחיד שעבד כראוי, ועם שינויים מינימליים הדרושים כדי להתאים למסד הנתונים שלי.
    תודה רבה!

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

    אני יודע שהנוסחה הזו עובדת, אבל אני לא יכול לראות היכן לוקחים בחשבון את רדיוס כדור הארץ. מישהו יכול להאיר אותי בבקשה?

  34. 49
  35. 50
  36. 52
  37. 53
  38. 55
  39. 56
  40. 58

    תודה שפרסמת את המאמר המועיל הזה,  
    אבל משום מה הייתי רוצה לשאול
    איך להשיג את המרחק בין הקואורדיות בתוך mysql db לקואורדינטות שמוכנסות ל- php על ידי המשתמש?
    לתאר בצורה ברורה יותר:
    1. המשתמש צריך להוסיף [id] לבחירת נתונים שצוינו מ- db והקואורדיטים של המשתמש עצמו
    2. קובץ ה- php מקבלים את נתוני היעד (קואורדיות) באמצעות [id] ואז מחשבים את המרחק בין המשתמש לנקודת היעד

    או פשוט יכול פשוט לקבל מרחק מהקוד למטה?

    $ qry = "בחר *, (((acos (sin ((". $ latitude. "* pi () / 180)) * sin ((` קו רוחב` * pi () / 180)) + cos ((". קו רוחב. "* pi () / 180)) * cos (('קו רוחב' * pi () / 180)) * cos (((('. קו אורך.' - 'קו אורך') * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) כמרחק מ- 'MyTable` WHERE מרחק> = ". $ מרחק." >>>> האם אוכל "להוציא" את המרחק מכאן?
    שוב תודה,
    טימי ס

  41. 60

    בסדר, כל מה שניסיתי לא עובד. כלומר, מה שיש לי עובד, אבל המרחקים רחוקים.

    האם מישהו יכול לראות מה לא בסדר בקוד הזה?

    אם (isset ($ _ POST ['הוגש'])) {$ z = $ _POST ['מיקוד']; $ r = $ _ POST ['רדיוס']; הד "תוצאות עבור". $ z; $ sql = mysql_query ("בחר DISTINCT m.zipcode, m.MktName, m.LocAddSt, m.LocAddCity, m.LocAddState, m.x1, m.y1, m.verified, z1.lat, z2.lon, z1. city, z1.state FROM mrk m, zip z1, zip z2 WHERE m.zipcode = z1.zipcode AND z2.zipcode = $ z AND (3963 * acos (truncate (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']. " ". $ שורה ['מיקוד']; $ latitude1 = $ row ['lat']; $ longitude1 = $ שורה ['lon']; $ latitude2 = $ שורה ['y1']; $ longitude2 = $ שורה ['x1']; $ city = $ row ['city']; $ state = $ row ['state']; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = מרחק ($ lat1, $ lon1, $ lat2, $ lon2); $ מאומת = $ שורה ['מאומת']; אם ($ מאומת == '1') {הד ""; הד "". $ חנות. ""; הד $ דיס. " רחוק מאוד"; הד ""; } אחר {הד "". $ חנות. ""; הד $ דיס. " רחוק מאוד"; הד ""; }}}

    קוד ה- functions.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; switch ($ unit) {case 'Mi': break; מקרה 'Km': $ מרחק = $ מרחק * 1.609344; } לחזור (עגול (מרחק $, 2)); }

    תודה מראש

  42. 61
  43. 62

    היי דאגלס, מאמר נהדר. מצאתי שההסבר שלך לגבי המושגים הגיאוגרפיים והקוד באמת מעניין. ההצעה היחידה שלי תהיה לרווח ולהכניס את הקוד לתצוגה (כמו Stackoverflow למשל). אני מבין שאתה רוצה לחסוך מקום, אך ריווח / הזחה של קוד רגיל יקל עלי, בתור מתכנת, לקרוא ולנתח. בכל מקרה, זה דבר קטן. המשיכו בעבודה הנהדרת.

  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 (('קו רוחב' * pi () / 180)) * cos (((". אורך $. "- 'קו אורך') * pi () / 180))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) ”;
    $ sql = 'בחר *,'. נוסחה $. ' כמרחק מהטבלה איפה 'נוסחת $'. ' <= '. מרחק $;

  51. 71
  52. 72

    תודה רבה על גזירת המאמר הזה. זה מאוד מועיל.
    תחילה נוצר PHP כפלטפורמת סקריפטים פשוטה בשם "דף הבית האישי". כיום PHP (קיצור של Hypertext Preprocessor) הוא חלופה לטכנולוגיית Active Server Pages (ASP) של מיקרוסופט.

    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) כמרחק מ- "register" ";

  54. 75

    תודה אדוני מתפתל בצורה מושלמת .. אבל יש לי שאלה אחת אם אני רוצה להפיק ללא נקודה עשרונית אז מה אני יכול לעשות ..?

    תודה מראש.

  55. 76

    שלום, בבקשה אני באמת אצטרך את עזרתך בנושא.

    הגשתי בקשה לקבל לשרת האינטרנט שלי http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = קו רוחב $
    -2.23389 = אורך $
    ו- 20 = המרחק שאני רוצה לאחזר

    עם זאת באמצעות הנוסחה שלך, היא מאחזרת את כל השורות ב- db שלי

    $ תוצאות = DB :: בחר (DB :: raw ("SELECT *, (((acos (sin ((". $ latitude. "* pi () / 180)) * sin ((lat * pi () / 180 )) + cos ((". $ קו רוחב." * pi () / 180)) * cos ((lat * pi () / 180)) * cos (((". אורך $." - לנג) * 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, "מרחק": 16079.294719663}, {"id": 2, "שם": "פיצריית החוף המזרחי של Amici", "כתובת": "790 Castro St, Mountain View, CA", "lat": 37.387138366699, "lng": -122.08323669434, "distance": 16079.175940152}, {"id": 3, "name": "Kapp's Pizza Bar & Grill", "address": "191 Castro St, Mountain View, CA", "lat": 37.393886566162, "Lng": - 122.07891845703, "distance": 16078.381373826}, {"id": 4, "name": "Round Table Pizza: Mountain View", "address": "570 N Shoreline Blvd, Mountain View, CA", "Lat": 37.402652740479, "lng": - 122.07935333252, "distance": 16077.420540582}, {"id": 5, "name": "Tony & Alba's Pizza & Pasta", "address": "619 Escuela Ave, Mountain View, CA "," lat ": 37.394012451172," lng ": - 122.09552764893," distance ": 16078.563225154}, {" id ": 6," name ":" Oregano's Wood-Fired Pizza "," address ":" 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 מיילים אבל זה מביא את כל השורות. בבקשה מה אני עושה לא בסדר

מה אתה חושב?

אתר זה משתמש Akismet כדי להפחית דואר זבל. למד כיצד הנתונים שלך מעובדים.