Nominatim::reverseGeocode()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 6
rs 10
1
<?php
2
3
namespace LeKoala\GeoTools\Services;
4
5
use Exception;
6
use LeKoala\GeoTools\Models\Address;
7
use LeKoala\GeoTools\Models\Country;
8
use LeKoala\GeoTools\Models\Coordinates;
9
use SilverStripe\Control\Email\Email;
10
11
/**
12
 * @link https://wiki.openstreetmap.org/wiki/Nominatim
13
 * @link https://github.com/maxhelias/php-nominatim
14
 */
15
class Nominatim implements Geocoder
16
{
17
    const API_URL = 'https://nominatim.openstreetmap.org/{service}';
18
    const SEARCH_SERVICE = 'search';
19
    const REVERSE_SERVICE = 'reverse';
20
    const LOOKUP_SERVICE = 'lookup';
21
22
    /**
23
     * @param ?string $query
24
     * @param array<int|string,mixed> $params countrycodes
25
     * @return Address
26
     * @throws Exception when there is a problem with the api, otherwise may return an empty address
27
     */
28
    protected function query($query, $params = [])
29
    {
30
        $service = self::SEARCH_SERVICE;
31
        if (isset($params['service'])) {
32
            $service = $params['service'];
33
            unset($params['service']);
34
        }
35
36
        if ($query) {
37
            $params['q'] = $query;
38
        }
39
40
        $url = self::API_URL;
41
        $url = str_replace('{service}', $service, $url);
42
43
        $defaultParams = [
44
            'email' => Email::config()->admin_email,
45
            'limit' => 1,
46
            'format' => 'jsonv2',
47
            'addressdetails' => 1
48
        ];
49
50
        $params = array_merge($defaultParams, $params);
51
52
        $url .= '?' . http_build_query($params);
53
54
        // Create a stream
55
        $opts = [
56
            "http" => [
57
                "method" => "GET",
58
                "header" => implode("\r\n", [
59
                    "Accept: application/json",
60
                    "Accept-Encoding: gzip, deflate, br",
61
                    "Accept-Language: en",
62
                    "Cache-Control: no-cache",
63
                    "Pragma: no-cache",
64
                    "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36",
65
                ])
66
            ]
67
        ];
68
        $context = stream_context_create($opts);
69
        $result = file_get_contents($url, false, $context);
70
        if (!$result) {
71
            throw new Exception("The api returned no result");
72
        }
73
74
        $data = json_decode($result, true);
75
        if (!$data) {
76
            throw new Exception("Failed to decode api results");
77
        }
78
79
        $location = [];
80
        $countryCode = $countryName = null;
81
        $lat = $lon = null;
82
83
        // in reverse geocoding, it's only one result
84
        if (isset($data['place_id'])) {
85
            $row = $data;
86
        } else {
87
            $row = $data[0];
88
        }
89
90
        if (isset($row['address'])) {
91
            $address = $row['address'];
92
            $location = [
93
                'streetName' => $address['road'] ?? $address['building'] ?? null,
94
                'streetNumber' => $address['house_number'] ?? null,
95
                'postalCode' => $address['postcode'] ?? null,
96
                'locality' => $address['city'] ?? null,
97
            ];
98
            $countryCode = $address['country_code'] ?? null;
99
            $countryName = $address['country'] ?? null;
100
        }
101
        if (!empty($row['lat'])) {
102
            $lat = $row['lat'];
103
            $lon = $row['lon'];
104
        }
105
106
        $country = new Country($countryCode, $countryName);
107
        $coordinates = new Coordinates($lat, $lon);
108
109
        return new Address($location, $country, $coordinates);
110
    }
111
112
    /**
113
     * @inheritDoc
114
     */
115
    public function reverseGeocode($lat, $lon, $params = [])
116
    {
117
        $params['service'] = self::REVERSE_SERVICE;
118
        $params['lat'] = $lat;
119
        $params['lon'] = $lon;
120
        return $this->query(null, $params);
121
    }
122
123
    /**
124
     * @inheritDoc
125
     */
126
    public function geocode($address, $params = [])
127
    {
128
        return $this->query($address, $params);
129
    }
130
}
131