Completed
Push — master ( 45727a...f921fb )
by Tobias
21:51
created

Nominatim::jsonResultToLocation()   F

Complexity

Conditions 13
Paths 576

Size

Total Lines 65

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 13

Importance

Changes 0
Metric Value
dl 0
loc 65
ccs 36
cts 36
cp 1
rs 3.1947
c 0
b 0
f 0
cc 13
nc 576
nop 2
crap 13

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 24
    public static function withOpenStreetMapServer(HttpClient $client, string $userAgent, string $referer = ''): self
58
    {
59 24
        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 24
    public function __construct(HttpClient $client, $rootUrl, string $userAgent, string $referer = '')
69
    {
70 24
        parent::__construct($client);
71
72 24
        $this->rootUrl = rtrim($rootUrl, '/');
73 24
        $this->userAgent = $userAgent;
74 24
        $this->referer = $referer;
75
76 24
        if (empty($this->userAgent)) {
77
            throw new InvalidArgument('The User-Agent must be set to use the Nominatim provider.');
78
        }
79 24
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84 15
    public function geocodeQuery(GeocodeQuery $query): Collection
85
    {
86 15
        $address = $query->getText();
87
88
        // This API doesn't handle IPs
89 15
        if (filter_var($address, FILTER_VALIDATE_IP)) {
90 3
            throw new UnsupportedOperation('The Nominatim provider does not support IP addresses.');
91
        }
92
93 12
        $url = $this->rootUrl
94 12
            .'/search?'
95 12
            .http_build_query([
96 12
                'format' => 'jsonv2',
97 12
                'q' => $address,
98 12
                'addressdetails' => 1,
99 12
                'limit' => $query->getLimit(),
100
            ]);
101
102 12
        $countrycodes = $query->getData('countrycodes');
103 12
        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 12
        $viewbox = $query->getData('viewbox');
118 12
        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 12
        $content = $this->executeQuery($url, $query->getLocale());
132
133 7
        $json = json_decode($content);
134 7
        if (is_null($json) || !is_array($json)) {
135
            throw InvalidServerResponse::create($url);
136
        }
137
138 7
        if (empty($json)) {
139 1
            return new AddressCollection([]);
140
        }
141
142 6
        $results = [];
143 6
        foreach ($json as $place) {
144 6
            $results[] = $this->jsonResultToLocation($place, false);
145
        }
146
147 6
        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 8
    private function jsonResultToLocation(\stdClass $place, bool $reverse): Location
190
    {
191 8
        $builder = new AddressBuilder($this->getName());
192
193 8
        foreach (['state', 'county'] as $i => $tagName) {
194 8
            if (isset($place->address->{$tagName})) {
195 8
                $builder->addAdminLevel($i + 1, $place->address->{$tagName}, '');
196
            }
197
        }
198
199
        // get the first postal-code when there are many
200 8
        if (isset($place->address->postcode)) {
201 8
            $postalCode = $place->address->postcode;
202 8
            if (!empty($postalCode)) {
203 8
                $postalCode = current(explode(';', $postalCode));
204
            }
205 8
            $builder->setPostalCode($postalCode);
206
        }
207
208 8
        $localityFields = ['city', 'town', 'village', 'hamlet'];
209 8
        foreach ($localityFields as $localityField) {
210 8
            if (isset($place->address->{$localityField})) {
211 8
                $localityFieldContent = $place->address->{$localityField};
212
213 8
                if (!empty($localityFieldContent)) {
214 8
                    $builder->setLocality($localityFieldContent);
215
216 8
                    break;
217
                }
218
            }
219
        }
220
221 8
        $builder->setStreetName($place->address->road ?? $place->address->pedestrian ?? null);
222 8
        $builder->setStreetNumber($place->address->house_number ?? null);
223 8
        $builder->setSubLocality($place->address->suburb ?? null);
224 8
        $builder->setCountry($place->address->country ?? null);
225 8
        $builder->setCountryCode(isset($place->address->country_code) ? strtoupper($place->address->country_code) : null);
226
227 8
        $builder->setCoordinates(floatval($place->lat), floatval($place->lon));
228
229 8
        $builder->setBounds($place->boundingbox[0], $place->boundingbox[2], $place->boundingbox[1], $place->boundingbox[3]);
230
231 8
        /** @var NominatimAddress $location */
232 8
        $location = $builder->build(NominatimAddress::class);
233 8
        $location = $location->withAttribution($place->licence);
234
        $location = $location->withDisplayName($place->display_name);
235 8
236 7
        if (isset($place->address->quarter)) {
237
            $location = $location->withQuarter($place->address->quarter);
238 8
        }
239 7
240
        if (isset($place->osm_id)) {
241
            $location = $location->withOSMId(intval($place->osm_id));
242 8
        }
243 6
        if (isset($place->osm_type)) {
244 6
            $location = $location->withOSMType($place->osm_type);
245
        }
246
247 8
        if (false === $reverse) {
248
            $location = $location->withCategory($place->category);
249
            $location = $location->withType($place->type);
250
        }
251
252
        return $location;
253 8
    }
254
255 8
    /**
256
     * {@inheritdoc}
257
     */
258
    public function getName(): string
259
    {
260
        return 'nominatim';
261
    }
262
263
    /**
264 21
     * @param string      $url
265
     * @param string|null $locale
266 21
     *
267 3
     * @return string
268 3
     */
269
    private function executeQuery(string $url, string $locale = null): string
270
    {
271
        if (null !== $locale) {
272 21
            $url .= '&'.http_build_query([
273 21
                'accept-language' => $locale,
274
            ]);
275 21
        }
276
277
        $request = $this->getRequest($url);
278
        $request = $request->withHeader('User-Agent', $this->userAgent);
279 21
280
        if (!empty($this->referer)) {
281
            $request = $request->withHeader('Referer', $this->referer);
282
        }
283
284
        return $this->getParsedResponse($request);
285
    }
286
}
287