SphericalDistance.php 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. <?php
  2. namespace App\Doctrine\ORM\AST;
  3. use Doctrine\ORM\Query\AST\ASTException;
  4. use Doctrine\ORM\Query\AST\Functions\FunctionNode;
  5. use Doctrine\ORM\Query\AST\Node;
  6. use Doctrine\ORM\Query\Lexer;
  7. use Doctrine\ORM\Query\Parser;
  8. use Doctrine\ORM\Query\QueryException;
  9. use Doctrine\ORM\Query\SqlWalker;
  10. /**
  11. * SphericalDistanceFunction ::= "SPHERICAL_DISTANCE" "(" ArithmeticPrimary "," ArithmeticPrimary "," ArithmeticPrimary "," ArithmeticPrimary ")".
  12. *
  13. * Calcule la distance en km à vol d'oiseau entre les coordonnées géographiques données (latitude, longitude) de deux points.
  14. *
  15. * Implémentation de la formule de Haversine, dont la précision est de l'ordre de la dizaine de mètres dans les cas les plus courants.
  16. *
  17. * Pour utiliser la fonction :
  18. *
  19. * SPHERICAL_DISTANCE(latitude1, longitude1, latitude2, longitude2)
  20. *
  21. * WARNING: passing latitude2 and longitude2 as parameter, even named, is not properly interpreted, pass them directly.
  22. *
  23. * @see https://fr.wikipedia.org/wiki/Coordonn%C3%A9es_sph%C3%A9riques
  24. * @see https://fr.wikipedia.org/wiki/Formule_de_haversine
  25. */
  26. class SphericalDistance extends FunctionNode
  27. {
  28. protected Node|string $latitude1;
  29. protected Node|string $longitude1;
  30. protected Node|string $latitude2;
  31. protected Node|string $longitude2;
  32. /**
  33. * Parse DQL Function.
  34. *
  35. * @throws QueryException
  36. */
  37. public function parse(Parser $parser): void
  38. {
  39. $parser->match(Lexer::T_IDENTIFIER);
  40. $parser->match(Lexer::T_OPEN_PARENTHESIS);
  41. $this->latitude1 = $parser->ArithmeticPrimary();
  42. $parser->match(Lexer::T_COMMA);
  43. $this->longitude1 = $parser->ArithmeticPrimary();
  44. $parser->match(Lexer::T_COMMA);
  45. $this->latitude2 = $parser->ArithmeticPrimary();
  46. $parser->match(Lexer::T_COMMA);
  47. $this->longitude2 = $parser->ArithmeticPrimary();
  48. $parser->match(Lexer::T_CLOSE_PARENTHESIS);
  49. }
  50. /**
  51. * Get SQL.
  52. *
  53. * @throws ASTException
  54. */
  55. public function getSql(SqlWalker $sqlWalker): string
  56. {
  57. $R = 6371; // Rayon terrestre, en km
  58. $lat2 = $this->latitude2->dispatch($sqlWalker);
  59. $lat1 = $this->latitude1->dispatch($sqlWalker);
  60. // Call two additional dispatch so doctrine complete the parameters stack (careful: the order is important)
  61. $this->latitude1->dispatch($sqlWalker);
  62. $this->latitude2->dispatch($sqlWalker);
  63. $lon1 = $this->longitude1->dispatch($sqlWalker);
  64. $lon2 = $this->longitude2->dispatch($sqlWalker);
  65. // Latitudes et longitudes en radians
  66. $rLat1 = "($lat1 * PI() / 180)";
  67. $rLon1 = "($lon1 * PI() / 180)";
  68. $rLat2 = "($lat2 * PI() / 180)";
  69. $rLon2 = "($lon2 * PI() / 180)";
  70. return "2 * $R * ASIN(SQRT(POW(SIN(($rLat2 - $rLat1) / 2), 2) + COS($rLat1) * COS($rLat2) * POW(SIN(($rLon2 - $rLon1) / 2), 2)))";
  71. }
  72. }