init(); $this->apiKey = $apiKey ?? $this->providerConfig('key'); } /** * Provider unique key * @return string */ public function providerKey(): string { return 'google'; } /** * Provider title * @return string */ public function providerTitle(): string { return 'Google Maps'; } /** * Config settings form * @param Form $form */ public function providerSettingsForm($form) { $form->text($this->providerConfigKey('key'), _t('geo', 'API key'), '', false) ->htmlAfter(_t('geo', 'Get API key [a]here[/a]', ['a' => '', '/a' => ''])) ->visibleIf('geo.geocode.provider', $this->providerKey()) ->sysAdmin($this->providerConfigKey('key')); } /** * Geocode query * @param string $query address * @param array $opts * @return array */ public function geocodeQuery($query, array $opts = []) { $query = $this->defaults($opts['query'] ?? [], [ 'address' => $query, ]); $response = $this->geocodeRequest($query, $opts); if (empty($response) || empty($response['results'])) { return []; } $i = 0; $locations = []; foreach ($response['results'] as $result) { $locations[] = [ 'lat' => $result['geometry']['location']['lat'], 'lng' => $result['geometry']['location']['lng'], ]; if (++$i >= $this->limit) { break; } } if ($this->limit === 1) { return reset($locations); } return $locations; } /** * Reverse geocode query * @param string|float $latitude * @param string|float $longitude * @param array|Closure $format * @param array $opts * @return array|string */ public function reverseQuery($latitude, $longitude, $format = self::FORMAT_DEFAULT, array $opts = []) { $query = $this->defaults($opts['query'] ?? [], [ 'latlng' => join(',', [$latitude, $longitude]), ]); $response = $this->geocodeRequest($query, $opts); if (empty($response) || empty($response['results'])) { return []; } $i = 0; $address = []; foreach ($response['results'] as $result) { $address[] = $this->formatAddress($result, $format); if (++$i >= $this->limit) { break; } } if ($this->limit === 1) { return reset($address); } return $address; } /** * Format address to specified type * @see https://developers.google.com/maps/documentation/geocoding/overview * @param array $result * @param array|Closure $format * @return string */ protected function formatAddress(array $result, $format): string { if ($format instanceof Closure) { return $format($result); } if ($format === static::FORMAT_FULL || empty($format) || !is_array($format)) { return $result['formatted_address']; } $known = []; foreach ($result['address_components'] as $addr) { $type = reset($addr['types']); switch ($type) { case 'country': $known[static::ADDRESS_COUNTRY] = $addr['long_name']; break; case 'locality': $known[static::ADDRESS_CITY] = $addr['long_name']; break; case 'route': $known[static::ADDRESS_STREET] = $addr['long_name']; break; case 'street_number': $known[static::ADDRESS_HOUSE] = $addr['long_name']; break; case 'postal_code': $known[static::ADDRESS_POSTALCODE] = $addr['short_name']; break; default: $known[$type] = $addr['long_name']; break; } } $address = []; foreach ($format as $type) { if (! empty($known[$type])) { $address[] = $known[$type]; } } return join(', ', $address); } /** * Send geocode request to API * @param array $query * @param array $opts * @return array|bool */ protected function geocodeRequest(array $query, array $opts = []) { $query['key'] = $this->apiKey; $this->addQueryLanguageAndRegion($query, $opts); $response = $this->request($this->geocodeUrl, $query); $json = $response->json(); if ($json === null) { $this->log('Invalid response'); return false; } if ($json['status'] === 'REQUEST_DENIED') { $this->log($json['error_message']); return false; } if ($json['status'] === 'OVER_QUERY_LIMIT') { $this->log('Daily quota exceeded'); return false; } if ($json['status'] !== 'OK') { $this->log('Invalid response'); return false; } return $json; } /** * Add language and region to query * @param array $query * @param array $opts * @return void */ protected function addQueryLanguageAndRegion(array &$query, array $opts = []) { list($language, $region) = explode('_', Lang::current('locale', 'ru_RU')); $query['language'] = $opts['language'] ?? $language; $query['region'] = $opts['region'] ?? $region; } }