Passed
Push — master ( 5eacca...720baf )
by
unknown
11:13
created

MapTiler.php (1 issue)

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\MapTiler;
14
15
use Geocoder\Collection;
16
use Geocoder\Exception\InvalidServerResponse;
17
use Geocoder\Exception\UnsupportedOperation;
18
use Geocoder\Http\Provider\AbstractHttpProvider;
19
use Geocoder\Location;
20
use Geocoder\Model\AddressBuilder;
21
use Geocoder\Model\AddressCollection;
22
use Geocoder\Model\Bounds;
23
use Geocoder\Provider\Provider;
24
use Geocoder\Query\GeocodeQuery;
25
use Geocoder\Query\ReverseQuery;
26
use Psr\Http\Client\ClientInterface;
27
28
/**
29
 * @author Jonathan Beliën
30
 */
31
final class MapTiler extends AbstractHttpProvider implements Provider
32
{
33
    /**
34
     * @var string
35
     */
36
    public const ENDPOINT_URL = 'https://api.maptiler.com/geocoding/%s.json?key=%s';
37
38
    /**
39
     * @var string
40
     */
41
    private $apiKey;
42
43
    /**
44
     * @param ClientInterface $client an HTTP client
45
     * @param string          $apiKey API key
46
     */
47
    public function __construct(ClientInterface $client, string $apiKey)
48
    {
49
        parent::__construct($client);
0 ignored issues
show
Deprecated Code introduced by
The function Geocoder\Http\Provider\A...Provider::__construct() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

49
        /** @scrutinizer ignore-deprecated */ parent::__construct($client);
Loading history...
50
51
        $this->apiKey = $apiKey;
52
    }
53
54
    public function geocodeQuery(GeocodeQuery $query): Collection
55
    {
56
        $address = $query->getText();
57
58
        // This API doesn't handle IPs
59
        if (filter_var($address, FILTER_VALIDATE_IP)) {
60
            throw new UnsupportedOperation('The MapTiler provider does not support IP addresses.');
61
        }
62
63
        $url = sprintf(self::ENDPOINT_URL, $address, $this->apiKey);
64
65
        $json = $this->executeQuery($url, $query->getLocale(), $query->getBounds());
66
67
        if (!isset($json->features) || empty($json->features)) {
68
            return new AddressCollection([]);
69
        }
70
71
        $results = [];
72
        foreach ($json->features as $feature) {
73
            $results[] = $this->featureToAddress($feature);
74
        }
75
76
        return new AddressCollection($results);
77
    }
78
79
    public function reverseQuery(ReverseQuery $query): Collection
80
    {
81
        $coordinates = $query->getCoordinates();
82
83
        $url = sprintf(self::ENDPOINT_URL, implode(',', $coordinates->toArray()), $this->apiKey);
84
85
        $json = $this->executeQuery($url, $query->getLocale());
86
87
        if (!isset($json->features) || empty($json->features)) {
88
            return new AddressCollection([]);
89
        }
90
91
        $results = [];
92
        foreach ($json->features as $feature) {
93
            $results[] = $this->featureToAddress($feature);
94
        }
95
96
        return new AddressCollection($results);
97
    }
98
99
    private function featureToAddress(\stdClass $feature): Location
100
    {
101
        $builder = new AddressBuilder($this->getName());
102
103
        $coordinates = 'Point' === $feature->geometry->type ? $feature->geometry->coordinates : $feature->center;
104
105
        $builder->setCoordinates(floatval($coordinates[1]), floatval($coordinates[0]));
106
107
        if (in_array('street', $feature->place_type, true)) {
108
            $builder->setStreetName($feature->text);
109
        } elseif (in_array('subcity', $feature->place_type, true)) {
110
            $builder->setSubLocality($feature->text);
111
        } elseif (in_array('city', $feature->place_type, true)) {
112
            $builder->setLocality($feature->text);
113
        }
114
115
        if (isset($feature->bbox)) {
116
            $builder->setBounds($feature->bbox[0], $feature->bbox[2], $feature->bbox[1], $feature->bbox[3]);
117
        }
118
119
        $this->extractFromContext($builder, $feature->context ?? []);
120
121
        return $builder->build();
122
    }
123
124
    /**
125
     * @param array<string, \stdClass> $context
126
     */
127
    private function extractFromContext(AddressBuilder &$builder, array $context): AddressBuilder
128
    {
129
        $cityContext = array_filter($context, function ($c) { return 1 === preg_match('/^city\.\d+$/', $c->id); });
130
        if (count($cityContext) > 0) {
131
            $city = current($cityContext);
132
            $builder->setLocality($city->text);
133
        }
134
135
        $countryContext = array_filter($context, function ($c) { return 1 === preg_match('/^country\.\d+$/', $c->id); });
136
        if (count($countryContext) > 0) {
137
            $country = current($countryContext);
138
            $builder->setCountry($country->text);
139
        }
140
141
        return $builder;
142
    }
143
144
    public function getName(): string
145
    {
146
        return 'maptiler';
147
    }
148
149
    private function executeQuery(string $url, ?string $locale = null, ?Bounds $bounds = null): \stdClass
150
    {
151
        $url .= '&'.http_build_query([
152
            'language' => $locale,
153
            'bbox' => !is_null($bounds) ? implode(',', $bounds->toArray()) : null,
154
        ]);
155
156
        $content = $this->getUrlContents($url);
157
158
        $json = json_decode($content);
159
160
        // API error
161
        if (is_null($json)) {
162
            throw InvalidServerResponse::create($url);
163
        }
164
165
        return $json;
166
    }
167
}
168