Mapzen::reverseQuery()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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\Mapzen;
14
15
use Geocoder\Collection;
16
use Geocoder\Exception\InvalidCredentials;
17
use Geocoder\Exception\QuotaExceeded;
18
use Geocoder\Exception\UnsupportedOperation;
19
use Geocoder\Model\Address;
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
 * Mapzen has shut down as their APIs as of February 1, 2018.
29
 *
30
 * @deprecated https://github.com/geocoder-php/Geocoder/issues/808
31
 */
32
final class Mapzen extends AbstractHttpProvider implements Provider
33
{
34
    /**
35
     * @var string
36
     */
37
    const GEOCODE_ENDPOINT_URL = 'https://search.mapzen.com/v1/search?text=%s&api_key=%s&size=%d';
38
39
    /**
40
     * @var string
41
     */
42
    const REVERSE_ENDPOINT_URL = 'https://search.mapzen.com/v1/reverse?point.lat=%f&point.lon=%f&api_key=%s&size=%d';
43
44
    /**
45
     * @var string
46
     */
47
    private $apiKey;
48
49
    /**
50
     * Mapzen has shut down as their APIs as of February 1, 2018.
51
     *
52
     * @deprecated https://github.com/geocoder-php/Geocoder/issues/808
53
     *
54
     * @param HttpClient $client an HTTP adapter
55
     * @param string     $apiKey an API key
56
     */
57 26
    public function __construct(HttpClient $client, string $apiKey)
58
    {
59 26
        if (empty($apiKey)) {
60
            throw new InvalidCredentials('No API key provided.');
61
        }
62
63 26
        $this->apiKey = $apiKey;
64 26
        parent::__construct($client);
65 26
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70 17
    public function geocodeQuery(GeocodeQuery $query): Collection
71
    {
72 17
        $address = $query->getText();
73
74
        // This API doesn't handle IPs
75 17
        if (filter_var($address, FILTER_VALIDATE_IP)) {
76 4
            throw new UnsupportedOperation('The Mapzen provider does not support IP addresses, only street addresses.');
77
        }
78
79 13
        $url = sprintf(self::GEOCODE_ENDPOINT_URL, urlencode($address), $this->apiKey, $query->getLimit());
80
81 13
        return $this->executeQuery($url);
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87 8
    public function reverseQuery(ReverseQuery $query): Collection
88
    {
89 8
        $coordinates = $query->getCoordinates();
90 8
        $longitude = $coordinates->getLongitude();
91 8
        $latitude = $coordinates->getLatitude();
92 8
        $url = sprintf(self::REVERSE_ENDPOINT_URL, $latitude, $longitude, $this->apiKey, $query->getLimit());
93
94 8
        return $this->executeQuery($url);
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100 7
    public function getName(): string
101
    {
102 7
        return 'mapzen';
103
    }
104
105
    /**
106
     * @param $url
107
     *
108
     * @return Collection
109
     */
110 21
    private function executeQuery(string $url): AddressCollection
111
    {
112 21
        $content = $this->getUrlContents($url);
113 11
        $json = json_decode($content, true);
114
115
        // See https://mapzen.com/documentation/search/api-keys-rate-limits/
116 11
        if (isset($json['meta'])) {
117 2
            switch ($json['meta']['status_code']) {
118 2
                case 403:
119 1
                    throw new InvalidCredentials('Invalid or missing api key.');
120 1
                case 429:
121 1
                    throw new QuotaExceeded('Valid request but quota exceeded.');
122
            }
123
        }
124
125 9
        if (!isset($json['type']) || 'FeatureCollection' !== $json['type'] || !isset($json['features']) || 0 === count($json['features'])) {
126 3
            return new AddressCollection([]);
127
        }
128
129 6
        $locations = $json['features'];
130
131 6
        if (empty($locations)) {
132
            return new AddressCollection([]);
133
        }
134
135 6
        $results = [];
136 6
        foreach ($locations as $location) {
137
            $bounds = [
138 6
                'south' => null,
139
                'west' => null,
140
                'north' => null,
141
                'east' => null,
142
            ];
143 6
            if (isset($location['bbox'])) {
144
                $bounds = [
145 5
                    'south' => $location['bbox'][3],
146 5
                    'west' => $location['bbox'][2],
147 5
                    'north' => $location['bbox'][1],
148 5
                    'east' => $location['bbox'][0],
149
                ];
150
            }
151
152 6
            $props = $location['properties'];
153
154 6
            $adminLevels = [];
155 6
            foreach (['region', 'county', 'locality', 'macroregion', 'country'] as $i => $component) {
156 6
                if (isset($props[$component])) {
157 6
                    $adminLevels[] = ['name' => $props[$component], 'level' => $i + 1];
158
                }
159
            }
160
161 6
            $results[] = Address::createFromArray([
162 6
                'providedBy' => $this->getName(),
163 6
                'latitude' => $location['geometry']['coordinates'][1],
164 6
                'longitude' => $location['geometry']['coordinates'][0],
165 6
                'bounds' => $bounds,
166 6
                'streetNumber' => isset($props['housenumber']) ? $props['housenumber'] : null,
167 6
                'streetName' => isset($props['street']) ? $props['street'] : null,
168 6
                'subLocality' => isset($props['neighbourhood']) ? $props['neighbourhood'] : null,
169 6
                'locality' => isset($props['locality']) ? $props['locality'] : null,
170 6
                'postalCode' => isset($props['postalcode']) ? $props['postalcode'] : null,
171 6
                'adminLevels' => $adminLevels,
172 6
                'country' => isset($props['country']) ? $props['country'] : null,
173 6
                'countryCode' => isset($props['country_a']) ? strtoupper($props['country_a']) : null,
174
            ]);
175
        }
176
177 6
        return new AddressCollection($results);
178
    }
179
180
    /**
181
     * @param array $components
182
     *
183
     * @return string|null
184
     */
185
    protected function guessLocality(array $components)
186
    {
187
        $localityKeys = ['city', 'town', 'village', 'hamlet'];
188
189
        return $this->guessBestComponent($components, $localityKeys);
190
    }
191
192
    /**
193
     * @param array $components
194
     *
195
     * @return string|null
196
     */
197
    protected function guessStreetName(array $components)
198
    {
199
        $streetNameKeys = ['road', 'street', 'street_name', 'residential'];
200
201
        return $this->guessBestComponent($components, $streetNameKeys);
202
    }
203
204
    /**
205
     * @param array $components
206
     *
207
     * @return string|null
208
     */
209
    protected function guessSubLocality(array $components)
210
    {
211
        $subLocalityKeys = ['neighbourhood', 'city_district'];
212
213
        return $this->guessBestComponent($components, $subLocalityKeys);
214
    }
215
216
    /**
217
     * @param array $components
218
     * @param array $keys
219
     *
220
     * @return string|null
221
     */
222
    protected function guessBestComponent(array $components, array $keys)
223
    {
224
        foreach ($keys as $key) {
225
            if (isset($components[$key]) && !empty($components[$key])) {
226
                return $components[$key];
227
            }
228
        }
229
230
        return null;
231
    }
232
}
233