<?php
/*
* This file is part of the Elcodi package.
*
* Copyright (c) 2014-2016 Elcodi Networks S.L.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* Feel free to edit as you please, and have fun.
*
* @author Marc Morera <yuhu@mmoreram.com>
* @author Aldo Chiecchia <zimage@tiscali.it>
* @author Elcodi Team <tech@elcodi.com>
*/
namespace Elcodi\Component\Geo\Adapter\LocationPopulator;
use Goodby\CSV\Import\Standard\Interpreter;
use Goodby\CSV\Import\Standard\Lexer;
use Goodby\CSV\Import\Standard\LexerConfig;
use Mmoreram\Extractor\Extractor;
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use Elcodi\Component\Geo\Adapter\LocationPopulator\Interfaces\LocationPopulatorAdapterInterface;
use Elcodi\Component\Geo\Entity\Interfaces\LocationInterface;
use Elcodi\Component\Geo\Services\LocationBuilder;
/**
* Class GeonamesLocationPopulatorAdapter.
*
* @author Berny Cantos <be@rny.cc>
*/
class GeonamesLocationPopulatorAdapter implements LocationPopulatorAdapterInterface
{
/**
* @var Extractor
*
* Extractor
*/
private $extractor;
/**
* @var LocationBuilder
*
* Location Builder
*/
private $locationBuilder;
/**
* Constructor.
*
* @param Extractor $extractor Extractor instance
* @param LocationBuilder $locationBuilder Location builder
*/
public function __construct(
Extractor $extractor,
LocationBuilder $locationBuilder
) {
$this->extractor = $extractor;
$this->locationBuilder = $locationBuilder;
}
/**
* Populate a country.
*
* @param string $countryCode Country Code
*
* @return LocationInterface Root location
*/
public function populate($countryCode)
{
$countryCode = strtoupper($countryCode);
$downloadedFilePath = $this->downloadFile($countryCode);
$extractedPath = $this->extractFile($downloadedFilePath);
$rootLocation = $this
->loadLocationsFrom(
$countryCode,
$extractedPath
);
return $rootLocation;
}
/**
* Download data file from country
* Avoids download if the file exists.
*
* @param string $countryCode Country code
*
* @return string
*/
private function downloadFile($countryCode)
{
$tempname = sys_get_temp_dir() . '/elcodi-locator-geonames-' . $countryCode . '.zip';
if (!file_exists($tempname)) {
$dirname = dirname($tempname);
if (!file_exists($dirname)) {
mkdir($dirname, 0777, true);
}
$downloadUrl = 'http://download.geonames.org/export/zip/' . $countryCode . '.zip';
copy($downloadUrl, $tempname);
}
return $tempname;
}
/**
* Download file and return new pathname of extracted content.
*
* The geoname's zipfile contains this structure:
* - {countryCode}.txt
* - readme.txt
*
* We're only interested in the first file.
*
* @param string $tempname temporary name for downloaded content
*
* @return string Pathname of the resulting file
*/
private function extractFile($tempname)
{
$extractedFiles = $this
->extractor
->extractFromFile($tempname)
->files()
->notName('readme.txt')
->getIterator();
$extractedFiles->rewind();
if (!$extractedFiles->valid()) {
throw new FileNotFoundException();
}
return $extractedFiles->current();
}
/**
* Extract data from de CSV and create new Location objects.
*
* @param string $countryCode Country code
* @param string $pathname Pathname
*
* @return LocationInterface Root location
*/
private function loadLocationsFrom(
$countryCode,
$pathname
) {
$countryInfo = $this->getCountryInfo($countryCode);
$country = $this
->locationBuilder
->addLocation(
$countryInfo[0],
$countryInfo[1],
$countryInfo[0],
'country'
);
$interpreter = new Interpreter();
$interpreter->unstrict();
$nbItems = 0;
$interpreter->addObserver(function (array $columns) use ($country, &$nbItems) {
if (
isset(
$columns[1],
$columns[2],
$columns[3],
$columns[4],
$columns[5],
$columns[6]
)
) {
++$nbItems;
$stateId = $this->normalizeId($columns[4]);
$state = $this
->locationBuilder
->addLocation(
$country->getId() . '_' . $stateId,
$columns[3],
$stateId,
'state',
$country
);
$provinceId = $this->normalizeId($columns[6]);
$province = $this
->locationBuilder
->addLocation(
$state->getId() . '_' . $provinceId,
$columns[5],
$provinceId,
'province',
$state
);
$cityId = $this->normalizeId($columns[2]);
$city = $this
->locationBuilder
->addLocation(
$province->getId() . '_' . $cityId,
$columns[2],
$cityId,
'city',
$province
);
$postalCodeId = $this->normalizeId($columns[1]);
$this
->locationBuilder
->addLocation(
$city->getId() . '_' . $postalCodeId,
$columns[1],
$postalCodeId,
'postalcode',
$city
);
}
});
$config = new LexerConfig();
$config->setDelimiter("\t");
$lexer = new Lexer($config);
$lexer->parse($pathname, $interpreter);
return $country;
}
/**
* Normalize Id, assuming it is a strange string.
*
* @param string $id Id
*
* @return string Normalized id
*/
private function normalizeId($id)
{
return strtolower(trim(preg_replace('~[^a-zA-Z\d]{1}~', '', $id)));
}
/**
* Get Country name from country code.
*
* @return string[] Country code and country name
*/
private function getCountryInfo($countryCode)
{
$countryNames = [
'AD' => ['AD', 'Andorra'],
'AE' => ['AE', 'United Arab Emirates'],
'AF' => ['AF', 'Afghanistan'],
'AG' => ['AG', 'Antigua and Barbuda'],
'AI' => ['AI', 'Anguilla'],
'AL' => ['AL', 'Albania'],
'AM' => ['AM', 'Armenia'],
'AO' => ['AO', 'Angola'],
'AQ' => ['AQ', 'Antarctica'],
'AR' => ['AR', 'Argentina'],
'AS' => ['AS', 'American Samoa'],
'AT' => ['AT', 'Austria'],
'AU' => ['AU', 'Australia'],
'AW' => ['AW', 'Aruba'],
'AX' => ['AX', 'Åland'],
'AZ' => ['AZ', 'Azerbaijan'],
'BA' => ['BA', 'Bosnia and Herzegovina'],
'BB' => ['BB', 'Barbados'],
'BD' => ['BD', 'Bangladesh'],
'BE' => ['BE', 'Belgium'],
'BF' => ['BF', 'Burkina Faso'],
'BG' => ['BG', 'Bulgaria'],
'BH' => ['BH', 'Bahrain'],