Completed
Push — master ( 645860...d918a8 )
by Tobias
04:12
created

LocationIQ   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 163
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 98.57%

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 7
dl 0
loc 163
ccs 69
cts 70
cp 0.9857
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
B geocodeQuery() 0 27 6
A reverseQuery() 0 19 3
B xmlResultToArray() 0 37 7
A getName() 0 4 1
A executeQuery() 0 8 2
A getGeocodeEndpointUrl() 0 4 1
A getReverseEndpointUrl() 0 4 1
A getNodeValue() 0 4 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\LocationIQ;
14
15
use Geocoder\Collection;
16
use Geocoder\Exception\InvalidServerResponse;
17
use Geocoder\Exception\InvalidCredentials;
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\Provider;
25
use Http\Client\HttpClient;
26
27
/**
28
 * @author Srihari Thalla <[email protected]>
29
 */
30
final class LocationIQ extends AbstractHttpProvider implements Provider
31
{
32
    /**
33
     * @var string
34
     */
35
    const BASE_API_URL = 'https://locationiq.org/v1';
36
37
    /**
38
     * @var string
39
     */
40
    private $apiKey;
41
42
    /**
43
     * @param HttpClient $client an HTTP adapter
44
     * @param string     $apiKey an API key
45
     */
46 17
    public function __construct(HttpClient $client, string $apiKey)
47
    {
48 17
        if (empty($apiKey)) {
49
            throw new InvalidCredentials('No API key provided.');
50
        }
51
52 17
        $this->apiKey = $apiKey;
53
54 17
        parent::__construct($client);
55 17
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60 9
    public function geocodeQuery(GeocodeQuery $query): Collection
61
    {
62 9
        $address = $query->getText();
63
64 9
        $url = sprintf($this->getGeocodeEndpointUrl(), urlencode($address), $query->getLimit());
65
66 9
        $content = $this->executeQuery($url, $query->getLocale());
67
68 4
        $doc = new \DOMDocument();
69 4
        if (!@$doc->loadXML($content) || null === $doc->getElementsByTagName('searchresults')->item(0)) {
70 2
            throw InvalidServerResponse::create($url);
71
        }
72
73 2
        $searchResult = $doc->getElementsByTagName('searchresults')->item(0);
74 2
        $places = $searchResult->getElementsByTagName('place');
75
76 2
        if (null === $places || 0 === $places->length) {
77 1
            return new AddressCollection([]);
78
        }
79
80 1
        $results = [];
81 1
        foreach ($places as $place) {
82 1
            $results[] = $this->xmlResultToArray($place, $place);
83
        }
84
85 1
        return new AddressCollection($results);
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91 8
    public function reverseQuery(ReverseQuery $query): Collection
92
    {
93 8
        $coordinates = $query->getCoordinates();
94 8
        $longitude = $coordinates->getLongitude();
95 8
        $latitude = $coordinates->getLatitude();
96 8
        $url = sprintf($this->getReverseEndpointUrl(), $latitude, $longitude, $query->getData('zoom', 18));
97 8
        $content = $this->executeQuery($url, $query->getLocale());
98
99 3
        $doc = new \DOMDocument();
100 3
        if (!@$doc->loadXML($content) || $doc->getElementsByTagName('error')->length > 0) {
101 1
            return new AddressCollection([]);
102
        }
103
104 2
        $searchResult = $doc->getElementsByTagName('reversegeocode')->item(0);
105 2
        $addressParts = $searchResult->getElementsByTagName('addressparts')->item(0);
106 2
        $result = $searchResult->getElementsByTagName('result')->item(0);
107
108 2
        return new AddressCollection([$this->xmlResultToArray($result, $addressParts)]);
109
    }
110
111
    /**
112
     * @param \DOMElement $resultNode
113
     * @param \DOMElement $addressNode
114
     *
115
     * @return Location
116
     */
117 3
    private function xmlResultToArray(\DOMElement $resultNode, \DOMElement $addressNode): Location
118
    {
119 3
        $builder = new AddressBuilder($this->getName());
120
121 3
        foreach (['state', 'county'] as $i => $tagName) {
122 3
            if (null !== ($adminLevel = $this->getNodeValue($addressNode->getElementsByTagName($tagName)))) {
123 3
                $builder->addAdminLevel($i + 1, $adminLevel, '');
124
            }
125
        }
126
127
        // get the first postal-code when there are many
128 3
        $postalCode = $this->getNodeValue($addressNode->getElementsByTagName('postcode'));
129 3
        if (!empty($postalCode)) {
130 3
            $postalCode = current(explode(';', $postalCode));
131
        }
132 3
        $builder->setPostalCode($postalCode);
133 3
        $builder->setStreetName($this->getNodeValue($addressNode->getElementsByTagName('road')) ?: $this->getNodeValue($addressNode->getElementsByTagName('pedestrian')));
134 3
        $builder->setStreetNumber($this->getNodeValue($addressNode->getElementsByTagName('house_number')));
135 3
        $builder->setLocality($this->getNodeValue($addressNode->getElementsByTagName('city')));
136 3
        $builder->setSubLocality($this->getNodeValue($addressNode->getElementsByTagName('suburb')));
137 3
        $builder->setCountry($this->getNodeValue($addressNode->getElementsByTagName('country')));
138 3
        $builder->setCoordinates($resultNode->getAttribute('lat'), $resultNode->getAttribute('lon'));
139
140 3
        $countryCode = $this->getNodeValue($addressNode->getElementsByTagName('country_code'));
141 3
        if (!is_null($countryCode)) {
142 3
            $builder->setCountryCode(strtoupper($countryCode));
143
        }
144
145 3
        $boundsAttr = $resultNode->getAttribute('boundingbox');
146 3
        if ($boundsAttr) {
147 3
            $bounds = [];
148 3
            list($bounds['south'], $bounds['north'], $bounds['west'], $bounds['east']) = explode(',', $boundsAttr);
149 3
            $builder->setBounds($bounds['south'], $bounds['north'], $bounds['west'], $bounds['east']);
150
        }
151
152 3
        return $builder->build();
153
    }
154
155
    /**
156
     * {@inheritdoc}
157
     */
158 3
    public function getName(): string
159
    {
160 3
        return 'locationiq';
161
    }
162
163
    /**
164
     * @param string      $url
165
     * @param string|null $locale
166
     *
167
     * @return string
168
     */
169 17
    private function executeQuery(string $url, string $locale = null): string
170
    {
171 17
        if (null !== $locale) {
172 3
            $url = sprintf('%s&accept-language=%s', $url, $locale);
173
        }
174
175 17
        return $this->getUrlContents($url);
176
    }
177
178 9
    private function getGeocodeEndpointUrl(): string
179
    {
180 9
        return self::BASE_API_URL.'/search.php?q=%s&format=xmlv1.1&addressdetails=1&normalizecity=1&limit=%d&key='.$this->apiKey;
181
    }
182
183 8
    private function getReverseEndpointUrl(): string
184
    {
185 8
        return self::BASE_API_URL.'/reverse.php?format=xmlv1.1&lat=%F&lon=%F&addressdetails=1&normalizecity=1&zoom=%d&key='.$this->apiKey;
186
    }
187
188 3
    private function getNodeValue(\DOMNodeList $element)
189
    {
190 3
        return $element->length ? $element->item(0)->nodeValue : null;
191
    }
192
}
193