Completed
Push — master ( 70fc0d...4949f4 )
by Tobias
01:22
created

Geonames.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Geocoder package.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT License
11
 */
12
13
namespace Geocoder\Provider\Geonames;
14
15
use Geocoder\Collection;
16
use Geocoder\Exception\InvalidCredentials;
17
use Geocoder\Exception\InvalidServerResponse;
18
use Geocoder\Exception\UnsupportedOperation;
19
use Geocoder\Model\AddressBuilder;
20
use Geocoder\Model\AddressCollection;
21
use Geocoder\Model\AdminLevelCollection;
22
use Geocoder\Provider\Geonames\Model\CountryInfo;
23
use Geocoder\Provider\Geonames\Model\GeonamesAddress;
24
use Geocoder\Query\GeocodeQuery;
25
use Geocoder\Query\ReverseQuery;
26
use Geocoder\Http\Provider\AbstractHttpProvider;
27
use Geocoder\Provider\Provider;
28
use Http\Client\HttpClient;
29
30
/**
31
 * @author Giovanni Pirrotta <[email protected]>
32
 */
33
final class Geonames extends AbstractHttpProvider implements Provider
34
{
35
    /**
36
     * @var string
37
     */
38
    const GEOCODE_ENDPOINT_URL = 'http://api.geonames.org/searchJSON?q=%s&maxRows=%d&style=full&username=%s';
39
40
    /**
41
     * @var string
42
     */
43
    const REVERSE_ENDPOINT_URL = 'http://api.geonames.org/findNearbyPlaceNameJSON?lat=%F&lng=%F&style=full&maxRows=%d&username=%s';
44
45
    /**
46
     * @var string
47
     */
48
    const BASE_ENDPOINT_URL = 'http://api.geonames.org/%s?username=%s';
49
50
    /**
51
     * @var string
52
     */
53
    private $username;
54
55
    /**
56
     * @param HttpClient $client   An HTTP adapter
57
     * @param string     $username Username login (Free registration at http://www.geonames.org/login)
58
     */
59 28
    public function __construct(HttpClient $client, string $username)
60
    {
61 28
        if (empty($username)) {
62
            throw new InvalidCredentials('No username provided.');
63
        }
64
65 28
        $this->username = $username;
66 28
        parent::__construct($client);
67 28
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72 12
    public function geocodeQuery(GeocodeQuery $query): Collection
73
    {
74 12
        $address = $query->getText();
75
76
        // This API doesn't handle IPs
77 12
        if (filter_var($address, FILTER_VALIDATE_IP)) {
78 2
            throw new UnsupportedOperation('The Geonames provider does not support IP addresses.');
79
        }
80
81 10
        $url = sprintf(self::GEOCODE_ENDPOINT_URL, urlencode($address), $query->getLimit(), $this->username);
82
83 10
        return $this->executeQuery($url, $query->getLocale());
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89 10
    public function reverseQuery(ReverseQuery $query): Collection
90
    {
91 10
        $coordinates = $query->getCoordinates();
92 10
        $longitude = $coordinates->getLongitude();
93 10
        $latitude = $coordinates->getLatitude();
94
95 10
        $url = sprintf(self::REVERSE_ENDPOINT_URL, $latitude, $longitude, $query->getLimit(), $this->username);
96
97 10
        return $this->executeQuery($url, $query->getLocale());
98
    }
99
100
    /**
101
     * @param string|null $country
102
     * @param string|null $locale
103
     *
104
     * @return array
105
     *
106
     * @throws \Geocoder\Exception\Exception
107
     */
108 5
    public function getCountryInfo(string $country = null, string $locale = null): array
109
    {
110 5
        $url = sprintf(self::BASE_ENDPOINT_URL, 'countryInfoJSON', $this->username);
111
112 5
        if (isset($country)) {
113 4
            $url = sprintf('%s&country=%s', $url, $country);
114
        }
115
116 5
        $url = sprintf('%s&style=FULL', $url);
117
118 5 View Code Duplication
        if (null !== $locale) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
119
            // Locale code transformation: for example from it_IT to it
120 1
            $url = sprintf('%s&lang=%s', $url, substr($locale, 0, 2));
121
        }
122
123 5
        $content = $this->getUrlContents($url);
124 5
        if (null === $json = json_decode($content)) {
125
            throw InvalidServerResponse::create($url);
126
        }
127
128 5
        $data = $json->geonames;
129
130 5
        if (empty($data)) {
131 1
            return [];
132
        }
133
134 4
        $results = [];
135
136 4
        foreach ($data as $item) {
137 4
            $countryInfo = new CountryInfo();
138
139 4
            $results[] = $countryInfo
140 4
                ->setBounds($item->south, $item->west, $item->north, $item->east)
141 4
                ->withContinent($item->continent ?? null)
142 4
                ->withCapital($item->capital ?? null)
143 4
                ->withLanguages($item->langesuages ?? '')
144 4
                ->withGeonameId($item->geonameId ?? null)
145 4
                ->withIsoAlpha3($item->isoAlpha3 ?? null)
146 4
                ->withFipsCode($item->fipsCode ?? null)
147 4
                ->withPopulation($item->population ?? null)
148 4
                ->withIsoNumeric($item->isoNumeric ?? null)
149 4
                ->withAreaInSqKm($item->areaInSqKm ?? null)
150 4
                ->withCountryCode($item->countryCode ?? null)
151 4
                ->withCountryName($item->countryName ?? null)
152 4
                ->withContinentName($item->continentName ?? null)
153 4
                ->withCurrencyCode($item->currencyCode ?? null);
154
        }
155
156 4
        return $results;
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162 7
    public function getName(): string
163
    {
164 7
        return 'geonames';
165
    }
166
167
    /**
168
     * @param string      $url
169
     * @param string|null $locale
170
     *
171
     * @return AddressCollection
172
     */
173 20
    private function executeQuery(string $url, string $locale = null): AddressCollection
174
    {
175 20 View Code Duplication
        if (null !== $locale) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176
            // Locale code transformation: for example from it_IT to it
177 4
            $url = sprintf('%s&lang=%s', $url, substr($locale, 0, 2));
178
        }
179
180 20
        $content = $this->getUrlContents($url);
181 10
        if (null === $json = json_decode($content)) {
182
            throw InvalidServerResponse::create($url);
183
        }
184
185 10
        if (isset($json->totalResultsCount) && empty($json->totalResultsCount)) {
186 2
            return new AddressCollection([]);
187
        }
188
189 8
        $data = $json->geonames;
190
191 8
        if (empty($data)) {
192 2
            return new AddressCollection([]);
193
        }
194
195 6
        $results = [];
196 6
        foreach ($data as $item) {
197 6
            $builder = new AddressBuilder($this->getName());
198
199 6
            if (isset($item->bbox)) {
200 2
                $builder->setBounds($item->bbox->south, $item->bbox->west, $item->bbox->north, $item->bbox->east);
201
            }
202
203 6
            for ($level = 1; $level <= AdminLevelCollection::MAX_LEVEL_DEPTH; ++$level) {
204 6
                $adminNameProp = 'adminName'.$level;
205 6
                $adminCodeProp = 'adminCode'.$level;
206 6
                if (!empty($item->$adminNameProp)) {
207 6
                    $builder->addAdminLevel($level, $item->$adminNameProp, $item->$adminCodeProp ?? null);
208
                }
209
            }
210
211 6
            $builder->setCoordinates($item->lat ?? null, $item->lng ?? null);
212 6
            $builder->setLocality($item->name ?? null);
213 6
            $builder->setCountry($item->countryName ?? null);
214 6
            $builder->setCountryCode($item->countryCode ?? null);
215 6
            $builder->setTimezone($item->timezone->timeZoneId ?? null);
216
217
            /** @var GeonamesAddress $address */
218 6
            $address = $builder->build(GeonamesAddress::class);
219 6
            $address = $address->withName($item->name ?? null);
220 6
            $address = $address->withAsciiName($item->asciiName ?? null);
221 6
            $address = $address->withFclName($item->fclName ?? null);
222 6
            $address = $address->withAlternateNames($item->alternateNames ?? []);
223 6
            $address = $address->withPopulation($item->population ?? null);
224 6
            $address = $address->withGeonameId($item->geonameId ?? null);
225 6
            $address = $address->withFcode($item->fcode ?? null);
226
227 6
            $results[] = $address;
228
        }
229
230 6
        return new AddressCollection($results);
231
    }
232
}
233