Files
oai-symfony/typo3conf/ext/tt_address/Classes/Service/GeocodeService.php
2026-07-03 17:53:31 +02:00

185 lines
6.7 KiB
PHP

<?php
declare(strict_types=1);
namespace FriendsOfTYPO3\TtAddress\Service;
use TYPO3\CMS\Core\Cache\CacheManager;
/**
* This file is part of the "tt_address" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Service for category related stuff
*
* thanks to https://github.com/b13/t3ext-geocoding for inspiration
*/
class GeocodeService implements SingletonInterface
{
/** @var int */
protected $cacheTime = 7776000;
/** @var string */
protected $geocodingUrl = 'https://maps.googleapis.com/maps/api/geocode/json?language=de&sensor=false';
public function __construct(string $googleMapsKey = '')
{
$this->geocodingUrl .= '&key=' . $googleMapsKey;
}
/**
* geocodes all missing records in a DB table and then stores the values
* in the DB record.
*
* only works if your DB table has the necessary fields
* helpful when calculating a batch of addresses and save the latitude/longitude automatically
*
* @param string $addWhereClause
* @return int
*/
public function calculateCoordinatesForAllRecordsInTable($addWhereClause = ''): int
{
$tableName = 'tt_address';
$latitudeField = 'latitude';
$longitudeField = 'longitude';
$streetField = 'address';
$zipField = 'zip';
$cityField = 'city';
$countryField = 'country';
// Fetch all records without latitude/longitude
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($tableName);
$queryBuilder = $connection->createQueryBuilder();
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$queryBuilder
->select('*')
->from($tableName)
->where(
$queryBuilder->expr()->or(
$queryBuilder->expr()->isNull($latitudeField),
$queryBuilder->expr()->eq($latitudeField, $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)),
$queryBuilder->expr()->eq($latitudeField, 0.00000000000),
$queryBuilder->expr()->isNull($longitudeField),
$queryBuilder->expr()->eq($longitudeField, $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)),
$queryBuilder->expr()->eq($longitudeField, 0.00000000000)
)
)
->setMaxResults(500);
if (!empty($addWhereClause)) {
$queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($addWhereClause));
}
$records = $queryBuilder->executeQuery()->fetchAllAssociative();
if (\count($records) > 0) {
foreach ($records as $record) {
$country = $record[$countryField];
// do the geocoding
if (!empty($record[$zipField]) || !empty($record[$cityField])) {
$coords = $this->getCoordinatesForAddress($record[$streetField], $record[$zipField], $record[$cityField], $country);
if ($coords) {
// Update the record to fill in the latitude and longitude values in the DB
$connection->update(
$tableName,
[
$latitudeField => $coords['latitude'],
$longitudeField => $coords['longitude'],
],
[
'uid' => $record['uid']
]
);
}
}
}
}
return \count($records);
}
/**
* core functionality: asks google for the coordinates of an address
* stores known addresses in a local cache.
*
* @param string $street
* @param string $zip
* @param string $city
* @param string $country
* @return array an array with latitude and longitude
*/
public function getCoordinatesForAddress($street = null, $zip = null, $city = null, $country = ''): array
{
$addressParts = [];
foreach ([$street, $zip . ' ' . $city, $country] as $addressPart) {
if (empty($addressPart)) {
continue;
}
$addressParts[] = trim($addressPart);
}
$address = ltrim(implode(',', $addressParts), ',');
if (empty($address)) {
return [];
}
$cacheObject = $this->initializeCache();
$cacheKey = 'geocode-' . strtolower(str_replace(' ', '-', preg_replace('/[^0-9a-zA-Z ]/m', '', $address)));
// Found in cache? Return it.
if ($cacheObject->has($cacheKey)) {
return $cacheObject->get($cacheKey);
}
$result = $this->getApiCallResult(
$this->geocodingUrl . '&address=' . urlencode($address)
);
if (empty($result) || empty($result['results']) || empty($result['results'][0]['geometry'])) {
return [];
}
$geometry = $result['results'][0]['geometry'];
$result = [
'latitude' => $geometry['location']['lat'],
'longitude' => $geometry['location']['lng'],
];
// Now store the $result in cache and return
$cacheObject->set($cacheKey, $result, [], $this->cacheTime);
return $result;
}
/**
* @param string $url
* @return array
*/
protected function getApiCallResult(string $url): array
{
$response = GeneralUtility::getUrl($url);
$result = json_decode($response, true);
if ($result['status'] !== 'OVER_QUERY_LIMIT') {
return $result;
}
return [];
}
/**
* Initializes the cache for the DB requests.
*
* @param string $name
* @return FrontendInterface Cache Object
*/
protected function initializeCache(string $name = 'ttaddress_geocoding'): FrontendInterface
{
try {
$cacheManager = GeneralUtility::makeInstance(CacheManager::class);
return $cacheManager->getCache($name);
} catch (NoSuchCacheException $e) {
throw new \RuntimeException('Unable to load Cache!', 1548785854);
}
}
}