Completed
Push — master ( d97cbc...a4ad66 )
by Tobias
06:24
created

Nominatim::executeQuery()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
crap 2
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\InvalidServerResponse;
17
use Geocoder\Exception\UnsupportedOperation;
18
use Geocoder\Location;
19
use Geocoder\Model\AddressBuilder;
20
use Geocoder\Model\AddressCollection;
21
use Geocoder\Query\GeocodeQuery;
22
use Geocoder\Query\ReverseQuery;
23
use Geocoder\Http\Provider\AbstractHttpProvider;
24
use Geocoder\Provider\LocaleAwareGeocoder;
25
use Geocoder\Provider\Provider;
26
use Http\Client\HttpClient;
27
28
/**
29
 * @author Niklas Närhinen <[email protected]>
30
 */
31
final class Nominatim extends AbstractHttpProvider implements LocaleAwareGeocoder, Provider
32
{
33
    /**
34
     * @var string
35
     */
36
    private $rootUrl;
37
38
    /**
39
     * @param HttpClient  $client
40
     * @param string|null $locale
0 ignored issues
show
Bug introduced by
There is no parameter named $locale. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
41
     *
42
     * @return Nominatim
43
     */
44 27
    public static function withOpenStreetMapServer(HttpClient $client)
45
    {
46 27
        return new self($client, 'https://nominatim.openstreetmap.org');
47
    }
48
49
    /**
50
     * @param HttpClient $client  an HTTP adapter
51
     * @param string     $rootUrl Root URL of the nominatim server
52
     */
53 27
    public function __construct(HttpClient $client, $rootUrl)
54
    {
55 27
        parent::__construct($client);
56
57 27
        $this->rootUrl = rtrim($rootUrl, '/');
58 27
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 18
    public function geocodeQuery(GeocodeQuery $query): Collection
64
    {
65 18
        $address = $query->getText();
66
        // This API does not support IPv6
67 18
        if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
68 2
            throw new UnsupportedOperation('The Nominatim provider does not support IPv6 addresses.');
69
        }
70
71 16
        if ('127.0.0.1' === $address) {
72 1
            return new AddressCollection([$this->getLocationForLocalhost()]);
73
        }
74
75 15
        $url = sprintf($this->getGeocodeEndpointUrl(), urlencode($address), $query->getLimit());
76 15
        $content = $this->executeQuery($url, $query->getLocale());
77
78 5
        $doc = new \DOMDocument();
79 5
        if (!@$doc->loadXML($content) || null === $doc->getElementsByTagName('searchresults')->item(0)) {
80 2
            throw InvalidServerResponse::create($url);
81
        }
82
83 3
        $searchResult = $doc->getElementsByTagName('searchresults')->item(0);
84 3
        $places = $searchResult->getElementsByTagName('place');
85
86 3
        if (null === $places || 0 === $places->length) {
87 1
            return new AddressCollection([]);
88
        }
89
90 2
        $results = [];
91 2
        foreach ($places as $place) {
92 2
            $results[] = $this->xmlResultToArray($place, $place);
93
        }
94
95 2
        return new AddressCollection($results);
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101 9
    public function reverseQuery(ReverseQuery $query): Collection
102
    {
103 9
        $coordinates = $query->getCoordinates();
104 9
        $longitude = $coordinates->getLongitude();
105 9
        $latitude = $coordinates->getLatitude();
106 9
        $url = sprintf($this->getReverseEndpointUrl(), $latitude, $longitude, $query->getData('zoom', 18));
107 9
        $content = $this->executeQuery($url, $query->getLocale());
108
109 4
        $doc = new \DOMDocument();
110 4
        if (!@$doc->loadXML($content) || $doc->getElementsByTagName('error')->length > 0) {
111 2
            return new AddressCollection([]);
112
        }
113
114 2
        $searchResult = $doc->getElementsByTagName('reversegeocode')->item(0);
115 2
        $addressParts = $searchResult->getElementsByTagName('addressparts')->item(0);
116 2
        $result = $searchResult->getElementsByTagName('result')->item(0);
117
118 2
        return new AddressCollection([$this->xmlResultToArray($result, $addressParts)]);
119
    }
120
121
    /**
122
     * @param \DOMElement $resultNode
123
     * @param \DOMElement $addressNode
124
     *
125
     * @return Location
126
     */
127 4
    private function xmlResultToArray(\DOMElement $resultNode, \DOMElement $addressNode): Location
128
    {
129 4
        $builder = new AddressBuilder($this->getName());
130 4
        $adminLevels = [];
0 ignored issues
show
Unused Code introduced by
$adminLevels is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
131
132 4
        foreach (['state', 'county'] as $i => $tagName) {
133 4
            if (null !== ($adminLevel = $this->getNodeValue($addressNode->getElementsByTagName($tagName)))) {
134 4
                $builder->addAdminLevel($i + 1, $adminLevel, '');
135
            }
136
        }
137
138
        // get the first postal-code when there are many
139 4
        $postalCode = $this->getNodeValue($addressNode->getElementsByTagName('postcode'));
140 4
        if (!empty($postalCode)) {
141 3
            $postalCode = current(explode(';', $postalCode));
142
        }
143 4
        $builder->setPostalCode($postalCode);
144 4
        $builder->setStreetName($this->getNodeValue($addressNode->getElementsByTagName('road')) ?: $this->getNodeValue($addressNode->getElementsByTagName('pedestrian')));
145 4
        $builder->setStreetNumber($this->getNodeValue($addressNode->getElementsByTagName('house_number')));
146 4
        $builder->setLocality($this->getNodeValue($addressNode->getElementsByTagName('city')));
147 4
        $builder->setSubLocality($this->getNodeValue($addressNode->getElementsByTagName('suburb')));
148 4
        $builder->setCountry($this->getNodeValue($addressNode->getElementsByTagName('country')));
149 4
        $builder->setCountryCode(strtoupper($this->getNodeValue($addressNode->getElementsByTagName('country_code'))));
150 4
        $builder->setCoordinates($resultNode->getAttribute('lat'), $resultNode->getAttribute('lon'));
151
152 4
        $boundsAttr = $resultNode->getAttribute('boundingbox');
153 4
        if ($boundsAttr) {
154 4
            $bounds = [];
155 4
            list($bounds['south'], $bounds['north'], $bounds['west'], $bounds['east']) = explode(',', $boundsAttr);
156 4
            $builder->setBounds($bounds['south'], $bounds['north'], $bounds['west'], $bounds['east']);
157
        }
158
159 4
        return $builder->build();
160
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165 5
    public function getName(): string
166
    {
167 5
        return 'nominatim';
168
    }
169
170
    /**
171
     * @param string      $url
172
     * @param string|null $locale
173
     *
174
     * @return string
175
     */
176 24
    private function executeQuery(string $url, string $locale = null): string
177
    {
178 24
        if (null !== $locale) {
179 4
            $url = sprintf('%s&accept-language=%s', $url, $locale);
180
        }
181
182 24
        return $this->getUrlContents($url);
183
    }
184
185 15
    private function getGeocodeEndpointUrl(): string
186
    {
187 15
        return $this->rootUrl.'/search?q=%s&format=xml&addressdetails=1&limit=%d';
188
    }
189
190 9
    private function getReverseEndpointUrl(): string
191
    {
192 9
        return $this->rootUrl.'/reverse?format=xml&lat=%F&lon=%F&addressdetails=1&zoom=%d';
193
    }
194
195 4
    private function getNodeValue(\DOMNodeList $element)
196
    {
197 4
        return $element->length ? $element->item(0)->nodeValue : null;
198
    }
199
}
200