Completed
Push — master ( 010a01...d74db1 )
by Tobias
03:10
created

Nominatim::geocodeQuery()   C

Complexity

Conditions 13
Paths 37

Size

Total Lines 65

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 13.2134

Importance

Changes 0
Metric Value
dl 0
loc 65
ccs 33
cts 37
cp 0.8919
rs 6.0569
c 0
b 0
f 0
cc 13
nc 37
nop 1
crap 13.2134

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Nominatim;
14
15
use Geocoder\Collection;
16
use Geocoder\Exception\InvalidArgument;
17
use Geocoder\Exception\InvalidServerResponse;
18
use Geocoder\Exception\UnsupportedOperation;
19
use Geocoder\Location;
20
use Geocoder\Model\AddressBuilder;
21
use Geocoder\Model\AddressCollection;
22
use Geocoder\Query\GeocodeQuery;
23
use Geocoder\Query\ReverseQuery;
24
use Geocoder\Http\Provider\AbstractHttpProvider;
25
use Geocoder\Provider\Provider;
26
use Geocoder\Provider\Nominatim\Model\NominatimAddress;
27
use Http\Client\HttpClient;
28
29
/**
30
 * @author Niklas Närhinen <[email protected]>
31
 * @author Jonathan Beliën <[email protected]>
32
 */
33
final class Nominatim extends AbstractHttpProvider implements Provider
34
{
35
    /**
36
     * @var string
37
     */
38
    private $rootUrl;
39
40
    /**
41
     * @var string
42
     */
43
    private $userAgent;
44
45
    /**
46
     * @var string
47
     */
48
    private $referer;
49
50
    /**
51
     * @param HttpClient $client    an HTTP client
52
     * @param string     $userAgent Value of the User-Agent header
53
     * @param string     $referer   Value of the Referer header
54
     *
55
     * @return Nominatim
56
     */
57 22
    public static function withOpenStreetMapServer(HttpClient $client, string $userAgent, string $referer = ''): self
58
    {
59 22
        return new self($client, 'https://nominatim.openstreetmap.org', $userAgent, $referer);
60
    }
61
62
    /**
63
     * @param HttpClient $client    an HTTP client
64
     * @param string     $rootUrl   Root URL of the nominatim server
65
     * @param string     $userAgent Value of the User-Agent header
66
     * @param string     $referer   Value of the Referer header
67
     */
68 22
    public function __construct(HttpClient $client, $rootUrl, string $userAgent, string $referer = '')
69
    {
70 22
        parent::__construct($client);
71
72 22
        $this->rootUrl = rtrim($rootUrl, '/');
73 22
        $this->userAgent = $userAgent;
74 22
        $this->referer = $referer;
75
76 22
        if (empty($this->userAgent)) {
77
            throw new InvalidArgument('The User-Agent must be set to use the Nominatim provider.');
78
        }
79 22
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84 13
    public function geocodeQuery(GeocodeQuery $query): Collection
85
    {
86 13
        $address = $query->getText();
87
88
        // This API doesn't handle IPs
89 13
        if (filter_var($address, FILTER_VALIDATE_IP)) {
90 3
            throw new UnsupportedOperation('The Nominatim provider does not support IP addresses.');
91
        }
92
93 10
        $url = $this->rootUrl
94 10
            .'/search?'
95 10
            .http_build_query([
96 10
                'format' => 'jsonv2',
97 10
                'q' => $address,
98 10
                'addressdetails' => 1,
99 10
                'limit' => $query->getLimit(),
100
            ]);
101
102 10
        $countrycodes = $query->getData('countrycodes');
103 10
        if (!is_null($countrycodes)) {
104 1
            if (is_array($countrycodes)) {
105
                $countrycodes = array_map('strtolower', $countrycodes);
106
107
                $url .= '&'.http_build_query([
108
                    'countrycodes' => implode(',', $countrycodes),
109
                ]);
110
            } else {
111 1
                $url .= '&'.http_build_query([
112 1
                    'countrycodes' => strtolower($countrycodes),
113
                ]);
114
            }
115
        }
116
117 10
        $viewbox = $query->getData('viewbox');
118 10
        if (!is_null($viewbox) && is_array($viewbox) && 4 === count($viewbox)) {
119 1
            $url .= '&'.http_build_query([
120 1
                'viewbox' => implode(',', $viewbox),
121
            ]);
122
123 1
            $bounded = $query->getData('bounded');
124 1
            if (!is_null($bounded) && true === $bounded) {
125 1
                $url .= '&'.http_build_query([
126 1
                    'bounded' => 1,
127
                ]);
128
            }
129
        }
130
131 10
        $content = $this->executeQuery($url, $query->getLocale());
132
133 5
        $json = json_decode($content);
134 5
        if (is_null($json) || !is_array($json)) {
135
            throw InvalidServerResponse::create($url);
136
        }
137
138 5
        if (empty($json)) {
139 1
            return new AddressCollection([]);
140
        }
141
142 4
        $results = [];
143 4
        foreach ($json as $place) {
144 4
            $results[] = $this->jsonResultToLocation($place, false);
145
        }
146
147 4
        return new AddressCollection($results);
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153 9
    public function reverseQuery(ReverseQuery $query): Collection
154
    {
155 9
        $coordinates = $query->getCoordinates();
156 9
        $longitude = $coordinates->getLongitude();
157 9
        $latitude = $coordinates->getLatitude();
158
159 9
        $url = $this->rootUrl
160 9
            .'/reverse?'
161 9
            .http_build_query([
162 9
                'format' => 'jsonv2',
163 9
                'lat' => $latitude,
164 9
                'lon' => $longitude,
165 9
                'addressdetails' => 1,
166 9
                'zoom' => $query->getData('zoom', 18),
167
            ]);
168
169 9
        $content = $this->executeQuery($url, $query->getLocale());
170
171 4
        $json = json_decode($content);
172 4
        if (is_null($json) || isset($json->error)) {
173 2
            return new AddressCollection([]);
174
        }
175
176 2
        if (empty($json)) {
177
            return new AddressCollection([]);
178
        }
179
180 2
        return new AddressCollection([$this->jsonResultToLocation($json, true)]);
181
    }
182
183
    /**
184
     * @param \stdClass $place
185
     * @param bool      $reverse
186
     *
187
     * @return Location
188
     */
189 6
    private function jsonResultToLocation(\stdClass $place, bool $reverse): Location
190
    {
191 6
        $builder = new AddressBuilder($this->getName());
192
193 6
        foreach (['state', 'county'] as $i => $tagName) {
194 6
            if (isset($place->address->{$tagName})) {
195 6
                $builder->addAdminLevel($i + 1, $place->address->{$tagName}, '');
196
            }
197
        }
198
199
        // get the first postal-code when there are many
200 6
        if (isset($place->address->postcode)) {
201 6
            $postalCode = $place->address->postcode;
202 6
            if (!empty($postalCode)) {
203 6
                $postalCode = current(explode(';', $postalCode));
204
            }
205 6
            $builder->setPostalCode($postalCode);
206
        }
207
208 6
        $localityFields = ['city', 'town', 'village', 'hamlet'];
209 6
        foreach ($localityFields as $localityField) {
210 6
            if (isset($place->address->{$localityField})) {
211 6
                $localityFieldContent = $place->address->{$localityField};
212
213 6
                if (!empty($localityFieldContent)) {
214 6
                    $builder->setLocality($localityFieldContent);
215
216 6
                    break;
217
                }
218
            }
219
        }
220
221 6
        $builder->setStreetName($place->address->road ?? $place->address->pedestrian ?? null);
222 6
        $builder->setStreetNumber($place->address->house_number ?? null);
223 6
        $builder->setSubLocality($place->address->suburb ?? null);
224 6
        $builder->setCountry($place->address->country);
225 6
        $builder->setCountryCode(strtoupper($place->address->country_code));
226
227 6
        $builder->setCoordinates(floatval($place->lat), floatval($place->lon));
228
229 6
        $builder->setBounds($place->boundingbox[0], $place->boundingbox[2], $place->boundingbox[1], $place->boundingbox[3]);
230
231 6
        $location = $builder->build(NominatimAddress::class);
232 6
        $location = $location->withAttribution($place->licence);
233 6
        $location = $location->withOSMId(intval($place->osm_id));
234 6
        $location = $location->withOSMType($place->osm_type);
235 6
        $location = $location->withDisplayName($place->display_name);
236
237 6
        if (false === $reverse) {
238 4
            $location = $location->withCategory($place->category);
239 4
            $location = $location->withType($place->type);
240
        }
241
242 6
        return $location;
243
    }
244
245
    /**
246
     * {@inheritdoc}
247
     */
248 6
    public function getName(): string
249
    {
250 6
        return 'nominatim';
251
    }
252
253
    /**
254
     * @param string      $url
255
     * @param string|null $locale
256
     *
257
     * @return string
258
     */
259 19
    private function executeQuery(string $url, string $locale = null): string
260
    {
261 19
        if (null !== $locale) {
262 3
            $url .= '&'.http_build_query([
263 3
                'accept-language' => $locale,
264
            ]);
265
        }
266
267 19
        $request = $this->getRequest($url);
268 19
        $request = $request->withHeader('User-Agent', $this->userAgent);
269
270 19
        if (!empty($this->referer)) {
271
            $request = $request->withHeader('Referer', $this->referer);
272
        }
273
274 19
        return $this->getParsedResponse($request);
275
    }
276
}
277