|
12 | 12 | use craft\db\Table; |
13 | 13 | use craft\elements\Address; |
14 | 14 | use craft\helpers\Db; |
| 15 | +use yii\db\Expression; |
15 | 16 |
|
16 | 17 | /** |
17 | 18 | * AddressQuery represents a SELECT SQL statement for categories in a way that is independent of DBMS. |
@@ -316,6 +317,26 @@ class AddressQuery extends ElementQuery implements NestedElementQueryInterface |
316 | 317 | */ |
317 | 318 | public ?string $lastName = null; |
318 | 319 |
|
| 320 | + /** |
| 321 | + * @var null|array Narrows the query results based on the distance to a set of coordinates. |
| 322 | + * --- |
| 323 | + * ```php |
| 324 | + * // Fetch addresses by distance to the given coordinates |
| 325 | + * $addresses = \craft\elements\Address::find() |
| 326 | + * ->distanceTo(['latitude' => 44.0473,'longitude' => -121.3338]) |
| 327 | + * ->all(); |
| 328 | + * ``` |
| 329 | + * ```twig |
| 330 | + * {# Fetch addresses by distance to the given coordinates #} |
| 331 | + * {% set addresses = craft.addresses() |
| 332 | + * .distanceTo({ latitude: 44.0473, longitude: -121.3338 }) |
| 333 | + * .all() %} |
| 334 | + * ``` |
| 335 | + * |
| 336 | + * @used-by distanceTo() |
| 337 | + */ |
| 338 | + public ?array $distanceTo = null; |
| 339 | + |
319 | 340 | /** |
320 | 341 | * Narrows the query results based on the country the addresses belong to. |
321 | 342 | * |
@@ -863,6 +884,31 @@ public function lastName(?string $value): static |
863 | 884 | return $this; |
864 | 885 | } |
865 | 886 |
|
| 887 | + /** |
| 888 | + * Calculate the distance of the address to a given set of coordinates, and |
| 889 | + * optionally narrows the query results by minimum or maximum distance. |
| 890 | + * Excludes addresses without valid coordinates. |
| 891 | + * |
| 892 | + * The coordinates should be provided as an associative array with the following keys: |
| 893 | + * |
| 894 | + * | Key | Value |
| 895 | + * | - | - |
| 896 | + * | `latitude` | Required, the latitude of the point to measure distance to. |
| 897 | + * | `longitude` | Required, the longitude of the point to measure distance to. |
| 898 | + * | `min` | Minimum distance in meters to narrow results by. |
| 899 | + * | `max` | Maximum distance in meters to narrow results by. |
| 900 | + * |
| 901 | + * @return static self reference |
| 902 | + * |
| 903 | + * @uses $distanceTo |
| 904 | + */ |
| 905 | + public function distanceTo(array $coordinates): static |
| 906 | + { |
| 907 | + $this->distanceTo = $coordinates; |
| 908 | + |
| 909 | + return $this; |
| 910 | + } |
| 911 | + |
866 | 912 | /** |
867 | 913 | * @inheritdoc |
868 | 914 | */ |
@@ -965,6 +1011,34 @@ protected function beforePrepare(): bool |
965 | 1011 | $this->subQuery->andWhere(Db::parseParam('addresses.fullName', $this->fullName)); |
966 | 1012 | } |
967 | 1013 |
|
| 1014 | + if ($this->distanceTo) { |
| 1015 | + $latitude = $this->distanceTo['latitude']; |
| 1016 | + $longitude = $this->distanceTo['longitude']; |
| 1017 | + $min = $this->distanceTo['min'] ?? null; |
| 1018 | + $max = $this->distanceTo['max'] ?? null; |
| 1019 | + |
| 1020 | + $distance = new Expression( |
| 1021 | + 'ST_Distance_Sphere( |
| 1022 | + POINT([[addresses.longitude]], [[addresses.latitude]]), |
| 1023 | + POINT(:lng, :lat) |
| 1024 | + )', |
| 1025 | + [':lng' => $longitude, ':lat' => $latitude] |
| 1026 | + ); |
| 1027 | + $this->subQuery->addSelect(['distance' => $distance]); |
| 1028 | + $this->query->addSelect(['distance' => $distance]); |
| 1029 | + |
| 1030 | + $this->subQuery->andWhere(['not', ['[[addresses.latitude]]' => null]]); |
| 1031 | + $this->subQuery->andWhere(['not', ['[[addresses.longitude]]' => null]]); |
| 1032 | + |
| 1033 | + if ($min) { |
| 1034 | + $this->query->andWhere(['>=', $distance, $min]); |
| 1035 | + } |
| 1036 | + |
| 1037 | + if ($max) { |
| 1038 | + $this->query->andWhere(['<=', $distance, $max]); |
| 1039 | + } |
| 1040 | + } |
| 1041 | + |
968 | 1042 | return true; |
969 | 1043 | } |
970 | 1044 |
|
|
0 commit comments