APIv4 - Add Address::getCoordinates action
authorColeman Watts <coleman@civicrm.org>
Thu, 26 May 2022 12:15:13 +0000 (08:15 -0400)
committerColeman Watts <coleman@civicrm.org>
Thu, 26 May 2022 16:28:28 +0000 (12:28 -0400)
CRM/Utils/Geocode/Google.php
Civi/Api4/Action/Address/GetCoordinates.php [new file with mode: 0644]
Civi/Api4/Address.php
Civi/Api4/Service/Spec/Provider/AddressGetSpecProvider.php

index ad670f6c94ffe796fde3d12bba175f70a4b98ca8..0885a3d3ae6a59daa4d31dc7e8a707c33b05af3e 100644 (file)
@@ -51,8 +51,6 @@ class CRM_Utils_Geocode_Google {
       return FALSE;
     }
 
-    $config = CRM_Core_Config::singleton();
-
     $add = '';
 
     if (!empty($values['street_address'])) {
@@ -99,6 +97,37 @@ class CRM_Utils_Geocode_Google {
       $add .= '+' . urlencode(str_replace('', '+', $values['country']));
     }
 
+    $coord = self::makeRequest($add);
+
+    $values['geo_code_1'] = $coord['geo_code_1'] ?? 'null';
+    $values['geo_code_2'] = $coord['geo_code_2'] ?? 'null';
+
+    if (isset($coord['geo_code_error'])) {
+      $values['geo_code_error'] = $coord['geo_code_error'];
+    }
+
+    return isset($coord['geo_code_1'], $coord['geo_code_2']);
+  }
+
+  /**
+   * @param string $address
+   *   Plain text address
+   * @return array
+   * @throws \GuzzleHttp\Exception\GuzzleException
+   */
+  public static function getCoordinates($address) {
+    return self::makeRequest(urlencode($address));
+  }
+
+  /**
+   * @param string $add
+   *   Url-encoded address
+   * @return array
+   * @throws \GuzzleHttp\Exception\GuzzleException
+   */
+  private static function makeRequest($add) {
+
+    $config = CRM_Core_Config::singleton();
     if (!empty($config->geoAPIKey)) {
       $add .= '&key=' . urlencode($config->geoAPIKey);
     }
@@ -115,7 +144,7 @@ class CRM_Utils_Geocode_Google {
     if ($xml === FALSE) {
       // account blocked maybe?
       CRM_Core_Error::debug_var('Geocoding failed.  Message from Google:', $string);
-      return FALSE;
+      return ['geo_code_error' => $string];
     }
 
     if (isset($xml->status)) {
@@ -126,23 +155,22 @@ class CRM_Utils_Geocode_Google {
       ) {
         $ret = $xml->result->geometry->location->children();
         if ($ret->lat && $ret->lng) {
-          $values['geo_code_1'] = (float) $ret->lat;
-          $values['geo_code_2'] = (float) $ret->lng;
-          return TRUE;
+          return [
+            'geo_code_1' => (float) $ret->lat,
+            'geo_code_2' => (float) $ret->lng,
+          ];
         }
       }
       elseif ($xml->status == 'ZERO_RESULTS') {
         // reset the geo code values if we did not get any good values
-        $values['geo_code_1'] = $values['geo_code_2'] = 'null';
-        return FALSE;
+        return [];
       }
       else {
         CRM_Core_Error::debug_var("Geocoding failed. Message from Google: ({$xml->status})", (string ) $xml->error_message);
-        $values['geo_code_1'] = $values['geo_code_2'] = 'null';
-        $values['geo_code_error'] = $xml->status;
-        return FALSE;
+        return ['geo_code_error' => $xml->status];
       }
     }
+    return [];
   }
 
 }
diff --git a/Civi/Api4/Action/Address/GetCoordinates.php b/Civi/Api4/Action/Address/GetCoordinates.php
new file mode 100644 (file)
index 0000000..86bcd16
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved.                        |
+ |                                                                    |
+ | This work is published under the GNU AGPLv3 license with some      |
+ | permitted exceptions and without any warranty. For full license    |
+ | and copyright information, see https://civicrm.org/licensing       |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Api4\Action\Address;
+
+use Civi\Api4\Generic\Result;
+
+/**
+ * Converts an address string to lat/long coordinates.
+ *
+ * @method $this setAddress(string $address)
+ * @method string getAddress()
+ */
+class GetCoordinates extends \Civi\Api4\Generic\AbstractAction {
+
+  /**
+   * Address string to convert to lat/long
+   *
+   * @var string
+   * @required
+   */
+  protected $address;
+
+  public function _run(Result $result) {
+    $coord = \CRM_Utils_Geocode_Google::getCoordinates($this->address);
+    if (isset($coord['geo_code_1'], $coord['geo_code_2'])) {
+      $result[] = $coord;
+    }
+    elseif (!empty($coord['geo_code_error'])) {
+      throw new \API_Exception('Geocoding failed. ' . $coord['geo_code_error']);
+    }
+  }
+
+}
index a4ec60aeb31eefeeac3fa2cd34681d97bd8f3877..c9db2a80150d569211bca93f329a8c14855c192f 100644 (file)
@@ -54,4 +54,13 @@ class Address extends Generic\DAOEntity {
       ->setCheckPermissions($checkPermissions);
   }
 
+  /**
+   * @param bool $checkPermissions
+   * @return Action\Address\GetCoordinates
+   */
+  public static function getCoordinates($checkPermissions = TRUE) {
+    return (new Action\Address\GetCoordinates(__CLASS__, __FUNCTION__))
+      ->setCheckPermissions($checkPermissions);
+  }
+
 }
index f3a5048caa26d68f2e6038cba0c223336a9b5abb..42765f3b15135e8aebd582868b3b5268c75e3e53 100644 (file)
@@ -12,6 +12,7 @@
 
 namespace Civi\Api4\Service\Spec\Provider;
 
+use Civi\Api4\Address;
 use Civi\Api4\Query\Api4SelectQuery;
 use Civi\Api4\Service\Spec\FieldSpec;
 use Civi\Api4\Service\Spec\RequestSpec;
@@ -64,6 +65,12 @@ class AddressGetSpecProvider implements Generic\SpecProviderInterface {
       $distance = $distance * 1000.00;
     }
 
+    if (!isset($value['geo_code_1'], $value['geo_code_2'])) {
+      $value = Address::getCoordinates(FALSE)
+        ->setAddress($value['address'])
+        ->execute()->first();
+    }
+
     if (
       isset($value['geo_code_1']) && is_numeric($value['geo_code_1']) &&
       isset($value['geo_code_2']) && is_numeric($value['geo_code_2'])
@@ -75,7 +82,6 @@ class AddressGetSpecProvider implements Generic\SpecProviderInterface {
         explode('.', $fieldAlias)[0]
       );
     }
-    // Todo: If address string given without lat/long, convert it.
 
     return '(0)';
   }