Completed
Push — master ( 31751e...dd6dbd )
by Tobias
01:06
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
        if (!isset($json->geonames)) {
129
            return [];
130
        }
131
132 5
        $data = $json->geonames;
133
134 5
        if (empty($data)) {
135 1
            return [];
136
        }
137
138 4
        $results = [];
139
140 4
        foreach ($data as $item) {
141 4
            $countryInfo = new CountryInfo();
142
143 4
            $results[] = $countryInfo
144 4
                ->setBounds($item->south, $item->west, $item->north, $item->east)
145 4
                ->withContinent($item->continent ?? null)
146 4
                ->withCapital($item->capital ?? null)
147 4
                ->withLanguages($item->langesuages ?? '')
148 4
                ->withGeonameId($item->geonameId ?? null)
149 4
                ->withIsoAlpha3($item->isoAlpha3 ?? null)
150 4
                ->withFipsCode($item->fipsCode ?? null)
151 4
                ->withPopulation($item->population ?? null)
152 4
                ->withIsoNumeric($item->isoNumeric ?? null)
153 4
                ->withAreaInSqKm($item->areaInSqKm ?? null)
154 4
                ->withCountryCode($item->countryCode ?? null)
155 4
                ->withCountryName($item->countryName ?? null)
156 4
                ->withContinentName($item->continentName ?? null)
157 4
                ->withCurrencyCode($item->currencyCode ?? null);
158
        }
159
160 4
        return $results;
161
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166 7
    public function getName(): string
167
    {
168 7
        return 'geonames';
169
    }
170
171
    /**
172
     * @param string      $url
173
     * @param string|null $locale
174
     *
175
     * @return AddressCollection
176
     */
177 20
    private function executeQuery(string $url, string $locale = null): AddressCollection
178
    {
179 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...
180
            // Locale code transformation: for example from it_IT to it
181 4
            $url = sprintf('%s&lang=%s', $url, substr($locale, 0, 2));
182
        }
183
184 20
        $content = $this->getUrlContents($url);
185 10
        if (null === $json = json_decode($content)) {
186
            throw InvalidServerResponse::create($url);
187
        }
188
189 10
        if (isset($json->totalResultsCount) && empty($json->totalResultsCount)) {
190 2
            return new AddressCollection([]);
191
        }
192
193 8
        $data = $json->geonames;
194
195 8
        if (empty($data)) {
196 2
            return new AddressCollection([]);
197
        }
198
199 6
        $results = [];
200 6
        foreach ($data as $item) {
201 6
            $builder = new AddressBuilder($this->getName());
202
203 6
            if (isset($item->bbox)) {
204 2
                $builder->setBounds($item->bbox->south, $item->bbox->west, $item->bbox->north, $item->bbox->east);
205
            }
206
207 6
            for ($level = 1; $level <= AdminLevelCollection::MAX_LEVEL_DEPTH; ++$level) {
208 6
                $adminNameProp = 'adminName'.$level;
209 6
                $adminCodeProp = 'adminCode'.$level;
210 6
                if (!empty($item->$adminNameProp)) {
211 6
                    $builder->addAdminLevel($level, $item->$adminNameProp, $item->$adminCodeProp ?? null);
212
                }
213
            }
214
215 6
            $builder->setCoordinates($item->lat ?? null, $item->lng ?? null);
216 6
            $builder->setLocality($item->name ?? null);
217 6
            $builder->setCountry($item->countryName ?? null);
218 6
            $builder->setCountryCode($item->countryCode ?? null);
219 6
            $builder->setTimezone($item->timezone->timeZoneId ?? null);
220
221
            /** @var GeonamesAddress $address */
222 6
            $address = $builder->build(GeonamesAddress::class);
223 6
            $address = $address->withName($item->name ?? null);
224 6
            $address = $address->withAsciiName($item->asciiName ?? null);
225 6
            $address = $address->withFclName($item->fclName ?? null);
226 6
            $address = $address->withAlternateNames($item->alternateNames ?? []);
227 6
            $address = $address->withPopulation($item->population ?? null);
228 6
            $address = $address->withGeonameId($item->geonameId ?? null);
229 6
            $address = $address->withFcode($item->fcode ?? null);
230
231 6
            $results[] = $address;
232
        }
233
234 6
        return new AddressCollection($results);
235
    }
236
}
237