GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Mapzen::geocodeQuery()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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