AzureMaps::formatGeocodeResponse()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 20
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 26
rs 9.6
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\AzureMaps;
14
15
use Geocoder\Collection;
16
use Geocoder\Exception\InvalidServerResponse;
17
use Geocoder\Http\Provider\AbstractHttpProvider;
18
use Geocoder\Model\AddressBuilder;
19
use Geocoder\Model\AddressCollection;
20
use Geocoder\Provider\Provider;
21
use Geocoder\Query\GeocodeQuery;
22
use Geocoder\Query\ReverseQuery;
23
use Http\Client\HttpClient;
24
use stdClass;
25
26
/**
27
 * @author Max Langerman <[email protected]>
28
 */
29
class AzureMaps extends AbstractHttpProvider implements Provider
30
{
31
    /**
32
     * @var string
33
     */
34
    const GEOCODE_ENDPOINT_SSL = 'https://atlas.microsoft.com/search/address';
35
36
    /**
37
     * @var string
38
     */
39
    const REVERSE_ENDPOINT_URL = 'https://atlas.microsoft.com/search/address/reverse';
40
41
    /**
42
     * @var array
43
     */
44
    private $options = [
45
        'typeahead' => null,
46
        'limit' => 5,
47
        'ofs' => null,
48
        'countrySet' => null,
49
        'lat' => null,
50
        'lon' => null,
51
        'radius' => null,
52
        'topLeft' => null,
53
        'btmRight' => null,
54
        'language' => 'en-US',
55
        'extendedPostalCodesFor' => null,
56
        'view' => null,
57
    ];
58
59
    /**
60
     * @var string
61
     */
62
    protected $subscriptionKey;
63
    /**
64
     * @var string
65
     */
66
    private $format;
67
68
    /**
69
     * AzureMaps constructor.
70
     */
71
    public function __construct(
72
        HttpClient $client,
73
        string $subscriptionKey,
74
        array $options = [],
75
        string $format = 'json'
76
    ) {
77
        parent::__construct($client);
78
79
        $this->subscriptionKey = $subscriptionKey;
80
        $this->format = $format;
81
        $this->setOptions($options);
82
    }
83
84
    /**
85
     * @throws \Geocoder\Exception\Exception
86
     */
87
    public function geocodeQuery(GeocodeQuery $query): Collection
88
    {
89
        $url = $this->buildGeocodeUrl($query->getText());
90
91
        $content = $this->getUrlContents($url);
92
93
        $response = $this->validateResponse($content, $url);
94
        $addresses = $this->formatGeocodeResponse($response);
95
96
        return new AddressCollection($addresses);
97
    }
98
99
    /**
100
     * @throws \Geocoder\Exception\Exception
101
     */
102
    public function reverseQuery(ReverseQuery $query): Collection
103
    {
104
        $url = $this->buildReverseGeocodeUrl(
105
            (string) $query->getCoordinates()->getLatitude(),
106
            (string) $query->getCoordinates()->getLongitude()
107
        );
108
109
        $content = $this->getUrlContents($url);
110
111
        $response = $this->validateResponse($content, $url);
112
        $addresses = $this->formatReverseGeocodeResponse($response);
113
114
        return new AddressCollection($addresses);
115
    }
116
117
    /**
118
     * Returns the provider's name.
119
     */
120
    public function getName(): string
121
    {
122
        return 'azure_maps';
123
    }
124
125
    /**
126
     * Returns an array of non null geocode /reverse-geocode options.
127
     */
128
    private function setOptions(array $options): void
129
    {
130
        $options = array_merge($this->options, $options);
131
132
        $this->options = array_filter($options, function ($option) {
133
            return !is_null($option);
134
        });
135
    }
136
137
    /**
138
     * Returns an array of keys to replace.
139
     */
140
    public function getOptions(): array
141
    {
142
        return $this->options;
143
    }
144
145
    private function buildGeocodeUrl(string $query): string
146
    {
147
        $url = self::GEOCODE_ENDPOINT_SSL;
148
        $format = $this->format;
149
        $subscriptionKey = $this->subscriptionKey;
150
        $options = http_build_query($this->getOptions());
151
        $query = urlencode($query);
152
153
        return sprintf(
154
            '%s/%s?api-version=1.0&subscription-key=%s&%s&query=%s',
155
            $url,
156
            $format,
157
            $subscriptionKey,
158
            $options,
159
            $query
160
        );
161
    }
162
163
    private function buildReverseGeocodeUrl(string $latitude, string $longitude): string
164
    {
165
        $url = self::REVERSE_ENDPOINT_URL;
166
        $format = $this->format;
167
        $subscriptionKey = $this->subscriptionKey;
168
        $options = http_build_query($this->getOptions());
169
170
        return sprintf(
171
            '%s/%s?api-version=1.0&subscription-key=%s&%s&query=%s,%s',
172
            $url,
173
            $format,
174
            $subscriptionKey,
175
            $options,
176
            $latitude,
177
            $longitude
178
        );
179
    }
180
181
    private function validateResponse(string $content, string $url): stdClass
182
    {
183
        $response = json_decode($content);
184
185
        if (!$response) {
186
            throw InvalidServerResponse::create($url);
187
        }
188
189
        if (isset($response->error)) {
190
            throw new InvalidServerResponse($response->error->message);
191
        }
192
193
        return $response;
194
    }
195
196
    private function formatGeocodeResponse(stdClass $response): array
197
    {
198
        return array_map(function ($result) {
199
            $builder = new AddressBuilder($this->getName());
200
            $coordinates = $result->position;
201
            $bounds = $result->viewport;
202
203
            $builder->setCoordinates($coordinates->lat, $coordinates->lon);
204
            $builder->setBounds(
205
                $bounds->btmRightPoint->lat,
206
                $bounds->topLeftPoint->lon,
207
                $bounds->topLeftPoint->lat,
208
                $bounds->btmRightPoint->lon
209
            );
210
            $builder->setValue('id', $result->id);
211
            $builder->setValue('type', $result->type);
212
            $builder->setValue('score', $result->score);
213
214
            $builder->setStreetName($result->address->streetName ?? null);
215
            $builder->setStreetNumber($result->address->streetNumber ?? null);
216
            $builder->setCountryCode($result->address->countryCode ?? null);
217
            $builder->setCountry($result->address->country ?? null);
218
            $builder->setPostalCode($result->address->extendedPostalCode ?? null);
219
220
            return $builder->build();
221
        }, $response->results);
222
    }
223
224
    private function formatReverseGeocodeResponse(stdClass $response): array
225
    {
226
        return array_filter(array_map(function ($address) {
227
            $coordinates = explode(',', $address->position);
228
            $latitude = array_shift($coordinates);
229
            $longitude = array_shift($coordinates);
230
231
            $bounds = $address->address->boundingBox;
232
            $southWest = explode(',', $bounds->southWest);
233
            $south = array_shift($southWest);
234
            $west = array_shift($southWest);
235
236
            $northEast = explode(',', $bounds->northEast);
237
            $north = array_shift($northEast);
238
            $east = array_shift($northEast);
239
240
            $builder = new AddressBuilder($this->getName());
241
            $builder->setCoordinates($latitude, $longitude);
242
            $builder->setBounds(
243
                $south,
244
                $west,
245
                $north,
246
                $east
247
            );
248
249
            $builder->setStreetName($address->address->streetName ?? null);
250
            $builder->setStreetNumber($address->address->streetNumber ?? null);
251
            $builder->setCountryCode($address->address->countryCode ?? null);
252
            $builder->setCountry($address->address->country ?? null);
253
            $builder->setPostalCode($address->address->extendedPostalCode ?? null);
254
            $builder->setLocality($address->address->municipality ?? null);
255
256
            return $builder->build();
257
        }, $response->addresses));
258
    }
259
}
260