From 2f57c4ad915e5209facaeeadacc3da7071998236 Mon Sep 17 00:00:00 2001 From: Moritz L'Hoest Date: Wed, 11 Jun 2025 18:53:15 +0200 Subject: [PATCH 1/2] Support distance calculation and limits in address queries --- src/elements/Address.php | 5 +++ src/elements/db/AddressQuery.php | 74 ++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/src/elements/Address.php b/src/elements/Address.php index b6bd39e7a07..c2b46e4b28c 100644 --- a/src/elements/Address.php +++ b/src/elements/Address.php @@ -295,6 +295,11 @@ public static function addressAttributeLabel(string $attribute, string $countryC */ public ?string $longitude = null; + /** + * @var float|null Distance in meters + */ + public ?float $distance = null; + /** * @inheritdoc */ diff --git a/src/elements/db/AddressQuery.php b/src/elements/db/AddressQuery.php index dd0fb8495c0..ff2ed1968e3 100644 --- a/src/elements/db/AddressQuery.php +++ b/src/elements/db/AddressQuery.php @@ -12,6 +12,7 @@ use craft\db\Table; use craft\elements\Address; use craft\helpers\Db; +use yii\db\Expression; /** * 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 */ public ?string $lastName = null; + /** + * @var null|array Narrows the query results based on the distance to a set of coordinates. + * --- + * ```php + * // Fetch addresses by distance to the given coordinates + * $addresses = \craft\elements\Address::find() + * ->distanceTo(['latitude' => 44.0473,'longitude' => -121.3338]) + * ->all(); + * ``` + * ```twig + * {# Fetch addresses by distance to the given coordinates #} + * {% set addresses = craft.addresses() + * .distanceTo({ latitude: 44.0473, longitude: -121.3338 }) + * .all() %} + * ``` + * + * @used-by distanceTo() + */ + public ?array $distanceTo = null; + /** * Narrows the query results based on the country the addresses belong to. * @@ -863,6 +884,31 @@ public function lastName(?string $value): static return $this; } + /** + * Calculate the distance of the address to a given set of coordinates, and + * optionally narrows the query results by minimum or maximum distance. + * Excludes addresses without valid coordinates. + * + * The coordinates should be provided as an associative array with the following keys: + * + * | Key | Value + * | - | - + * | `latitude` | Required, the latitude of the point to measure distance to. + * | `longitude` | Required, the longitude of the point to measure distance to. + * | `min` | Minimum distance in meters to narrow results by. + * | `max` | Maximum distance in meters to narrow results by. + * + * @return static self reference + * + * @uses $distanceTo + */ + public function distanceTo(array $coordinates): static + { + $this->distanceTo = $coordinates; + + return $this; + } + /** * @inheritdoc */ @@ -965,6 +1011,34 @@ protected function beforePrepare(): bool $this->subQuery->andWhere(Db::parseParam('addresses.fullName', $this->fullName)); } + if ($this->distanceTo) { + $latitude = $this->distanceTo['latitude']; + $longitude = $this->distanceTo['longitude']; + $min = $this->distanceTo['min'] ?? null; + $max = $this->distanceTo['max'] ?? null; + + $distance = new Expression( + 'ST_Distance_Sphere( + POINT([[addresses.longitude]], [[addresses.latitude]]), + POINT(:lng, :lat) + )', + [':lng' => $longitude, ':lat' => $latitude] + ); + $this->subQuery->addSelect(['distance' => $distance]); + $this->query->addSelect(['distance' => $distance]); + + $this->subQuery->andWhere(['not', ['[[addresses.latitude]]' => null]]); + $this->subQuery->andWhere(['not', ['[[addresses.longitude]]' => null]]); + + if ($min) { + $this->query->andWhere(['>=', $distance, $min]); + } + + if ($max) { + $this->query->andWhere(['<=', $distance, $max]); + } + } + return true; } From c455ffa1af8faebfa130edf083f8d4d79a09e7d0 Mon Sep 17 00:00:00 2001 From: Moritz L'Hoest Date: Tue, 7 Oct 2025 11:58:25 +0200 Subject: [PATCH 2/2] Fix an error with pagination --- src/elements/db/AddressQuery.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/elements/db/AddressQuery.php b/src/elements/db/AddressQuery.php index ff2ed1968e3..811b2fb296f 100644 --- a/src/elements/db/AddressQuery.php +++ b/src/elements/db/AddressQuery.php @@ -1032,10 +1032,12 @@ protected function beforePrepare(): bool if ($min) { $this->query->andWhere(['>=', $distance, $min]); + $this->subQuery->andWhere(['>=', $distance, $min]); } if ($max) { $this->query->andWhere(['<=', $distance, $max]); + $this->subQuery->andWhere(['<=', $distance, $max]); } }