PHP en SQL: Bereken of voer een query uit op de afstand van de grote cirkel tussen de breedtegraad en lengtegraad met de Haversine-formule

Haversine-formule - Bereken de grootcirkelafstand met PHP of MySQL

Deze maand heb ik behoorlijk wat in PHP en MySQL geprogrammeerd met betrekking tot GIS. Terwijl ik over het net snuffelde, had ik eigenlijk moeite om een ​​aantal van de Geografische berekeningen om de afstand tussen twee locaties te vinden, dus ik wilde ze hier delen.

Vluchtkaart Europa Met Grote Cirkelafstand

De eenvoudige manier om een ​​afstand tussen twee punten te berekenen, is door de Pythagoras-formule te gebruiken om de hypotenusa van een driehoek (A² + B² = C²) te berekenen. Dit staat bekend als de Euclidische afstand.

Dat is een interessant begin, maar het is niet van toepassing op Geografie, aangezien de afstand tussen de lengte- en breedtegraad dat wel is niet een gelijke afstand deel. Naarmate je dichter bij de evenaar komt, raken de breedtegraden verder uit elkaar. Als je een of andere eenvoudige driehoeksvergelijking gebruikt, kan deze de afstand nauwkeurig meten op de ene locatie en vreselijk verkeerd op de andere, vanwege de kromming van de aarde.

Grote Cirkel Afstand

De routes die over lange afstanden rond de aarde worden afgelegd, staan ​​bekend als de Grote Cirkel Afstand​ Dat wil zeggen ... de kortste afstand tussen twee punten op een bol is anders dan de punten op een platte kaart. Combineer dat met het feit dat de lengte- en breedtegraadlijnen niet op gelijke afstand staan… en je hebt een moeilijke berekening.

Hier is een fantastische video-uitleg over hoe Great Circles werken.

De Haversine-formule

De afstand met behulp van de kromming van de aarde is verwerkt in de Haversine-formule, dat trigonometrie gebruikt om rekening te houden met de kromming van de aarde. Als je de afstand tussen 2 plaatsen op aarde (hemelsbreed) vindt, is een rechte lijn eigenlijk een boog.

Dit is van toepassing op vliegreizen - heb je ooit naar de daadwerkelijke kaart van vluchten gekeken en opgemerkt dat ze gebogen zijn? Dat komt omdat het korter is om in een boog tussen twee punten te vliegen dan rechtstreeks naar de locatie.

PHP: Bereken de afstand tussen 2 punten van breedte- en lengtegraad

Hoe dan ook, hier is de PHP-formule voor het berekenen van de afstand tussen twee punten (samen met de conversie van mijlen versus kilometers), afgerond op twee decimalen.

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: alle records binnen een bereik ophalen door de afstand in mijlen te berekenen met behulp van breedte- en lengtegraad

Het is ook mogelijk om SQL te gebruiken om een ​​berekening uit te voeren om alle records binnen een bepaalde afstand te vinden. In dit voorbeeld ga ik MyTable in MySQL opvragen om alle records te vinden die kleiner zijn dan of gelijk zijn aan de variabele $ afstand (in mijlen) naar mijn locatie op $ latitude en $ longitude:

De query voor het ophalen van alle records binnen een specifiek afstand door de afstand in mijlen tussen twee punten van breedte- en lengtegraad te berekenen:

$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."

U moet dit aanpassen:

  • $ lengtegraad - dit is een PHP-variabele waarbij ik de lengtegraad van het punt passeer.
  • $ breedtegraad - dit is een PHP-variabele waarbij ik de lengtegraad van het punt passeer.
  • $ afstand - dit is de afstand waarop u alle records kleiner of gelijk wilt vinden.
  • tafel - dit is de tabel ... je wilt die vervangen door je tafelnaam.
  • breedte - dit is het veld van uw breedtegraad.
  • lengte - dit is het veld van uw lengtegraad.

SQL: alle records binnen een bereik ophalen door afstand in kilometers te berekenen met behulp van breedte- en lengtegraad

En hier is de SQL-query met kilometers in 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."

U moet dit aanpassen:

  • $ lengtegraad - dit is een PHP-variabele waarbij ik de lengtegraad van het punt passeer.
  • $ breedtegraad - dit is een PHP-variabele waarbij ik de lengtegraad van het punt passeer.
  • $ afstand - dit is de afstand waarop u alle records kleiner of gelijk wilt vinden.
  • tafel - dit is de tabel ... je wilt die vervangen door je tafelnaam.
  • breedte - dit is het veld van uw breedtegraad.
  • lengte - dit is het veld van uw lengtegraad.

Ik heb deze code gebruikt in een kaartplatform voor ondernemingen dat we gebruikten voor een winkel met meer dan 1,000 locaties in Noord-Amerika en het werkte uitstekend.

76 Reacties

  1. 1

    Heel erg bedankt voor het delen. Dit was een gemakkelijke kopieer- en plakopdracht en werkt prima. Je hebt me veel tijd bespaard.
    Ter info voor iedereen die naar C port:
    double deg2rad (double deg) {return deg * (3.14159265358979323846 / 180.0); }

  2. 2

    Heel mooi stukje posten - werkte heel goed - ik hoefde alleen de naam van de tafel met de lat-long te veranderen. Het werkt behoorlijk snel naar .. Ik heb een redelijk klein aantal lat-longs (<400) maar ik denk dat dit mooi zou opschalen. Leuke site ook - ik heb hem zojuist toegevoegd aan mijn del.icio.us-account en kom regelmatig terug.

  3. 4
  4. 5
  5. 8

    ik denk dat je SQL een statement nodig heeft.
    in plaats van WAAR afstand <= $ afstand die u mogelijk nodig heeft
    gebruik HAVING-afstand <= $ afstand

    anders bedankt dat je me een hoop tijd en energie hebt bespaard.

  6. 10
  7. 11
  8. 12

    Heel erg bedankt voor het delen van deze code. Het heeft me veel ontwikkeltijd bespaard. Dank ook aan uw lezers om erop te wijzen dat een HAVING-statement nodig is voor MySQL 5.x. Erg behulpzaam.

  9. 14
  10. 15

    Hallo,

    Een andere vraag. Is er een formule voor NMEA-strings zoals hieronder?

    1342.7500, N, 10052.2287, E

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

    Hartelijk dank,
    Harry

  11. 16

    Ik ontdekte ook dat WHERE niet voor mij werkte. Veranderde het in HAVING en alles werkt perfect. In eerste instantie las ik de opmerkingen niet en herschreef ik het met een geneste selectie. Beide werken prima.

  12. 17
  13. 18

    Ontzettend behulpzaam, heel erg bedankt! Ik had wat problemen met het nieuwe "HAVING", in plaats van "WAAR", maar toen ik de opmerkingen hier eenmaal las (na ongeveer een half uur van frustratie met mijn tandenknarsen = P), kreeg ik het goed aan het werk. Dank u ^ _ ^

  14. 19
  15. 20

    Houd er rekening mee dat zo'n selecte bewering erg rekenkundig intens en daarom traag zal zijn. Als u veel van die vragen heeft, kan het snel vastlopen.

    Een veel minder intense benadering is om een ​​eerste (ruwe) selectie uit te voeren met behulp van een VIERKANT gebied gedefinieerd door een berekende afstand, dwz “selecteer * van tafelnaam waar de breedtegraad tussen lat1 en lat2 en lengtegraad tussen lon1 en lon2”. lat1 = targetlatitude - latdiff, lat2 = targetlatitude + latdiff, vergelijkbaar met lon. latdiff ~ = afstand / 111 (voor km), of afstand / 69 voor mijlen aangezien 1 breedtegraad ~ 111 km is (lichte variatie aangezien de aarde enigszins ovaal is, maar voldoende voor dit doel). londiff = afstand / (abs (cos (deg2rad (breedtegraad)) * 111)) - of 69 voor mijlen (je kunt eigenlijk een iets groter vierkant nemen om rekening te houden met variaties). Neem dan het resultaat daarvan en voer het in de radiale selectie. Vergeet niet rekening te houden met coördinaten buiten het bereik - dwz het bereik van de acceptabele lengtegraad is -180 tot +180 en het bereik van de acceptabele breedtegraad is -90 tot +90 - voor het geval uw latdiff of londiff buiten dit bereik valt . Merk op dat dit in de meeste gevallen niet van toepassing is, omdat het alleen berekeningen beïnvloedt over een lijn door de Stille Oceaan van pool tot pool, hoewel het een deel van Chukotka en een deel van Alaska doorsnijdt.

    Wat we hiermee bereiken, is een aanzienlijke vermindering van het aantal punten waartegen u deze berekening maakt. Als u een miljoen globale punten in de database ongeveer gelijkmatig verdeeld heeft en u wilt binnen 100 km zoeken, dan is uw eerste (snelle) zoekopdracht een gebied van 10000 km20 en zal waarschijnlijk ongeveer 500 resultaten opleveren (gebaseerd op een gelijkmatige verdeling over een oppervlakte van ongeveer 20 miljoen vierkante kilometer), wat betekent dat u de complexe afstandsberekening XNUMX keer voor deze zoekopdracht uitvoert in plaats van een miljoen keer.

    • 21
      • 22

        Fantastisch advies! Ik werkte eigenlijk met een ontwikkelaar die een functie schreef die het binnenste vierkant trok en vervolgens een recursieve functie die 'vierkanten' rond de omtrek maakte om de resterende punten op te nemen en uit te sluiten. Het resultaat was een ongelooflijk snel resultaat - hij kon miljoenen punten in microseconden evalueren.

        Mijn benadering hierboven is beslist 'grof' maar capabel. Nogmaals bedankt!

        • 23

          Doug,

          Ik heb geprobeerd mysql en php te gebruiken om te evalueren of een lat lang punt binnen een polygoon ligt. Weet je of je vriend van je ontwikkelaar voorbeelden heeft gepubliceerd over hoe je deze taak kunt volbrengen? Of ken je goede voorbeelden. Bij voorbaat dank.

  16. 24

    Hallo allemaal, dit is mijn SQL-testverklaring:

    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

    en Mysql vertelt me ​​dat afstand, niet bestaat als een kolom, ik kan order by gebruiken, ik kan het doen zonder WAAR, en het werkt, maar niet ermee ...

  17. 26

    Dit is geweldig, maar het is net zoals de vogels vliegen. Het zou geweldig zijn om de google maps API hier op de een of andere manier in op te nemen (misschien via wegen enz.). Gewoon om een ​​idee te geven met een andere vorm van transport. Ik moet nog een gesimuleerde gloeifunctie in PHP maken die een efficiënte oplossing zou kunnen bieden voor het handelsreizigersprobleem. Maar ik denk dat ik misschien een deel van uw code kan hergebruiken om dit te doen.

  18. 27
  19. 28

    Goed artikel! Ik heb veel artikelen gevonden die beschrijven hoe de afstand tussen twee punten moet worden berekend, maar ik was echt op zoek naar het SQL-fragment.

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

    2 dagen onderzoek om eindelijk deze pagina te vinden die mijn probleem oplost. Het lijkt erop dat ik maar beter mijn WolframAlpha eruit kan halen en mijn wiskunde kan opfrissen. De wijziging van WAAR naar HAVING heeft mijn script in goede staat. DANK JE

  25. 37
    • 38
  26. 39

    Ik wou dat dit de eerste pagina was die ik hierop had gevonden. Na veel verschillende commando's geprobeerd te hebben, was dit de enige die goed werkte, en met minimale wijzigingen die nodig waren om in mijn eigen database te passen.
    Heel erg bedankt!

  27. 40

    Ik wou dat dit de eerste pagina was die ik hierop had gevonden. Na veel verschillende commando's geprobeerd te hebben, was dit de enige die goed werkte, en met minimale wijzigingen die nodig waren om in mijn eigen database te passen.
    Heel erg bedankt!

  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

    bedankt voor het plaatsen van dit nuttige artikel,  
    maar om de een of andere reden zou ik het willen vragen
    hoe de afstand te krijgen tussen de coördinaten in mysql db en de coördinaten die door de gebruiker in php zijn ingevoegd?
    voor een duidelijker omschrijving:
    1.gebruiker moet [id] invoegen voor het selecteren van gespecificeerde gegevens van db en de coördinaten van de gebruiker zelf
    2. het php-bestand verkrijg de doelgegevens (coördinaten) met behulp van [id] en bereken vervolgens de afstand tussen de gebruiker en het doelpunt

    of kan je gewoon afstand krijgen van de onderstaande code?

    $ qry = “SELECTEER *, (((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) als afstand VAN `MyTable` WAAR afstand> =". $ Afstand. " >>>> kan ik de afstand van hier “wegnemen”?
    nogmaals bedankt,
    Timmy S.

  41. 60

    ok, alles wat ik heb geprobeerd, werkt niet. Ik bedoel, wat ik heb werkt, maar de afstanden zijn ver weg.

    Kan iemand mogelijk zien wat er mis is met deze code?

    if (isset ($ _ POST ['ingediend'])) {$ z = $ _POST ['postcode']; $ r = $ _POST ['straal']; echo "Resultaten voor". $ z; $ sql = mysql_query (“SELECTEER 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 VAN mrk m, zip z1, zip z2 WAAR m.zipcode = z1.zipcode EN 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 ") of sterven (mysql_error ()); while ($ row = mysql_fetch_array ($ sql)) {$ store1 = $ row ['MktName']. ""; $ store = $ row ['LocAddSt']. ””; $ store. = $ row ['LocAddCity']. ",". $ row ['LocAddState']. " “. $ Row ['postcode']; $ latitude1 = $ rij ['lat']; $ longitude1 = $ rij ['lon']; $ latitude2 = $ rij ['y1']; $ longitude2 = $ rij ['x1']; $ city = $ row ['city']; $ state = $ row ['state']; $ dis = getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi'); // $ dis = afstand ($ lat1, $ lon1, $ lat2, $ lon2); $ geverifieerd = $ rij ['geverifieerd']; if ($ geverifieerd == '1') {echo “”; echo "". $ store. ""; echo $ dis. "Mijl (en) weg"; echo ""; } else {echo “”. $ store. ””; echo $ dis. "Mijl (en) weg"; echo ""; }}}

    mijn functies.php-code
    functie getnew ($ latitude1, $ longitude1, $ latitude2, $ longitude2, $ unit = 'Mi') {$ theta = $ longitude1 - $ longitude2; $ afstand = (sin (deg2rad ($ latitude1)) * sin (deg2rad ($ latitude2))) + (cos (deg2rad ($ latitude1)) * cos (deg2rad ($ latitude2)) * cos (deg2rad ($ theta)) ); $ afstand = acos ($ afstand); $ afstand = rad2deg ($ afstand); $ afstand = $ afstand * 60 * 1.1515; switch ($ unit) {case 'Mi': pauze; geval 'Km': $ afstand = $ afstand * 1.609344; } return (ronde ($ afstand, 2)); }

    Dank u bij voorbaat

  42. 61
  43. 62

    Hey Douglas, geweldig artikel. Ik vond uw uitleg van de geografische concepten en de code erg interessant. Mijn enige suggestie zou zijn om de code te spaties en te laten inspringen voor weergave (zoals Stackoverflow, bijvoorbeeld). Ik begrijp dat je ruimte wilt besparen, maar conventionele code-afstand / inspringing zou het voor mij als programmeur een stuk gemakkelijker maken om te lezen en te ontleden. Hoe dan ook, dat is een kleinigheid. Ga zo door.

  44. 64
  45. 65
  46. 66
  47. 67
  48. 68
  49. 69
  50. 70

    het lijkt sneller (mysql 5.9) om tweemaal de formule te gebruiken in de select en waarbij:
    $ formule = "(((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 = 'SELECTEER *,'. $ formule. ' als afstand VAN tabel WAAR '.. $ formule.' <= '. $ afstand;

  51. 71
  52. 72

    Heel erg bedankt voor het scheren van dit artikel. Het is erg nuttig.
    PHP werd in eerste instantie gemaakt als een eenvoudig scriptplatform genaamd "Personal Home Page". Tegenwoordig is PHP (de afkorting van Hypertext Preprocessor) een alternatief voor de Active Server Pages (ASP) -technologie van Microsoft.

    PHP is een open source server-side taal die wordt gebruikt voor het maken van dynamische webpagina's. Het kan in HTML worden ingesloten. PHP wordt meestal gebruikt in combinatie met een MySQL-database op Linux / UNIX-webservers. Het is waarschijnlijk de meest populaire scripttaal.

  53. 73

    Ik vond dat bovenstaande oplossing niet goed werkte.
    Ik moet veranderen naar:

    $ qqq = "SELECTEER *, (((acos (sin ((". $ latitude. "* pi () / 180)) * sin ((` latt` * pi () / 180)) + cos ((". $ latitude. "* pi () / 180)) * cos ((` latt` * pi () / 180)) * cos ((((". $ longitude." - `longt`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515) als afstand FROM `register`“;

  54. 75
  55. 76

    Hallo, ik heb je hulp hierbij echt nodig.

    Ik heb een verzoek ingediend bij mijn webserver http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ breedtegraad
    -2.23389 = $ lengtegraad
    en 20 = de afstand die ik wil ophalen

    Als u uw formule gebruikt, worden alle rijen in mijn database opgehaald

    $ results = 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) als afstand VAN markeringen MET afstand> = “. $ Afstand));

    [{"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": "Amici's East Coast Pizzeria", "address": "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}]

    Ik wil alleen rijen met 20 mijl ophalen, maar het brengt alle rijen. Wat doe ik alsjeblieft verkeerd?

Wat denk je?

Deze site gebruikt Akismet om spam te verminderen. Ontdek hoe uw reactiegegevens worden verwerkt.